-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathjps.hh
1539 lines (1281 loc) · 46.5 KB
/
jps.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#pragma once
/*
Public domain Jump Point Search implementation -- very fast pathfinding for uniform cost grids.
Scroll down for compile config, usage tips, example code.
License:
Public domain, WTFPL, CC0 or your favorite permissive license; whatever is available in your country.
Dependencies:
libc (stdlib.h, math.h) by default, change defines below to use your own functions. (realloc(), free(), sqrt())
Compiles as C++98, does not require C++11 nor the STL.
Does not throw exceptions, works without RTTI, does not contain any virtual methods.
Thread safety:
No global state. Searcher instances are not thread-safe. Grid template class is up to you.
If your grid access is read-only while pathfinding you may have many threads compute paths at the same time,
each with its own Searcher instance.
Background:
If you want to generate paths on a map with the following properties:
- You have a 2D grid (exactly two dimensions!), where each tile has exactly 8 neighbors (up, down, left, right + diagonals)
- There is no "cost" -- a tile is either walkable, or not.
then you may want to avoid full fledged A* and go for Jump Point Search (this lib).
JPS is usually much faster than plain old A*, as long as your tile traversability check function is fast.
Origin:
https://github.com/fgenesis/tinypile/blob/master/jps.hh
Based on my older implementation:
https://github.com/fgenesis/jps/blob/master/JPS.h
(For changes compared to that version go to the end of this file)
Inspired by:
http://users.cecs.anu.edu.au/~dharabor/data/papers/harabor-grastien-aaai11.pdf (The original paper)
https://github.com/Yonaba/Jumper
https://github.com/qiao/PathFinding.js
Usage:
Define a class that overloads `operator()(x, y) const`, returning a value that can be treated as boolean.
You are responsible for bounds checking!
You want your operator() to be as fast and small as possible, as it will be called a LOT.
Ask your compiler to force-inline it if possible.
// --- Begin example code ---
struct MyGrid
{
inline bool operator()(unsigned x, unsigned y) const // coordinates must be unsigned; method must be const
{
if(x < width && y < height) // Unsigned will wrap if < 0
... return true if terrain at (x, y) is walkable.
// return false if terrain is not walkable or out-of-bounds.
}
unsigned width, height;
};
// Then you can retrieve a path:
MyGrid grid(... set grid width, height, map data, whatever);
const unsigned step = 0; // 0 compresses the path as much as possible and only records waypoints.
// Set this to 1 if you want a detailed single-step path
// (e.g. if you plan to further mangle the path yourself),
// or any other higher value to output every Nth position.
// (Waypoints are always output regardless of the step size.)
JPS::PathVector path; // The resulting path will go here.
// You may also use std::vector or whatever, as long as your vector type
// has push_back(), begin(), end(), resize() methods (same semantics as std::vector).
// Note that the path will NOT include the starting position!
// --> If called with start == end it will report that a path has been found,
// but the resulting path vector will be empty!
// Single-call interface:
// (Further remarks about this function can be found near the bottom of this file.)
// Note that the path vector is NOT cleared! New path points are appended at the end.
bool found = JPS::findPath(path, grid, startx, starty, endx, endy, step);
// --- Alternatively, if you want more control & efficiency for repeated pathfinding runs: ---
// Use a Searcher instance (can be a class member, on the stack, ...)
// Make sure the passed grid reference stays valid throughout the searcher's lifetime.
// If you need control over memory allocation, you may pass an extra pointer that will be
// forwarded to your own JPS_realloc & JPS_free if you've set those. Otherwise it's ignored.
JPS::Searcher<MyGrid> search(grid, userPtr = NULL);
// build path incrementally from waypoints:
JPS::Position a, b, c, d = <...>; // set some waypoints
if (search.findPath(path, a, b)
&& search.findPath(path, b, c)
&& search.findPath(path, c, d))
{
// found path: a->b->c->d
}
// keep re-using existing pathfinder instance
while(whatever)
{
// Set startx, starty, endx, endy = <...>
if(!search.findPath(path, JPS::Pos(startx, starty), JPS::Pos(endx, endy), step))
{
// ...handle failure...
}
}
// If necessary, you may free internal memory -- this is never required; neither for performance, nor for correct function.
// If you do pathfinding after freeing memory, it'll allocate new memory.
// Note that freeing memory aborts any incremental search currently ongoing.
search.freeMemory();
// If you need to know how much memory is internally allocated by a searcher:
unsigned bytes = search.getTotalMemoryInUse();
// -------------------------------
// --- Incremental pathfinding ---
// -------------------------------
Calling JPS::findPath() or Searcher<>::findPath() always computes an entire path or returns failure.
If the path is long or costly and you have a tight CPU budget per frame you may want to perform pathfinding incrementally,
stretched over multiple frames.
First, call
### JPS_Result res = search.findPathInit(Position start, Position end) ###
Don't forget to check the return value, as it may return:
- JPS_NO_PATH if one or both of the points are obstructed
- JPS_EMPTY_PATH if the points are equal and not obstructed
- JPS_FOUND_PATH if the initial greedy heuristic could find a path quickly.
- JPS_OUT_OF_MEMORY if... well yeah.
If it returns JPS_NEED_MORE_STEPS then the next part can start.
Repeatedly call
### JPS_Result res = search.findPathStep(int limit) ###
until it returns JPS_NO_PATH or JPS_FOUND_PATH, or JPS_OUT_OF_MEMORY.
For consistency, you will want to ensure that the grid does not change between subsequent calls;
if the grid changes, parts of the path may go through a now obstructed area or may be no longer optimal.
If limit is 0, it will perform the pathfinding in one go. Values > 0 pause the search
as soon as possible after the number of steps was exceeded, returning NEED_MORE_STEPS.
Use search.getStepsDone() after some test runs to find a good value for the limit.
After getting JPS_FOUND_PATH, generate the actual path points via
### JPS_Result res = search.findPathFinish(PathVector& path, unsigned step = 0) ###
As described above, path points are appended, and granularity can be adjusted with the step parameter.
Returns JPS_FOUND_PATH if the path was successfully built and appended to the path vector.
Returns JPS_NO_PATH if the pathfinding did not finish or generating the path failed.
May return JPS_OUT_OF_MEMORY if the path vector must be resized but fails to allocate.
If findPathInit() or findPathStep() return JPS_OUT_OF_MEMORY, the current searcher progress becomes undefined.
To recover, free some memory elsewhere and call findPathInit() to try again.
If findPathFinish() returns out-of-memory but previous steps finished successfully,
then the found path is still valid for generating the path vector.
In that case you may call findPathFinish() again after making some memory available.
If you do not worry about memory, treat JPS_OUT_OF_MEMORY as if JPS_NO_PATH was returned.
You may pass JPS::PathVector, std::vector, or your own to findPathFinish().
Note that if the path vector type you pass throws exceptions in case of allocation failures (std::vector does, for example),
you'll get that exception, and the path vector will be in whatever state it was in when the last element was successfully inserted.
If no exception is thrown (ie. you used JPS::PathVector) then the failure cases do not modify the path vector.
You may abort a search anytime by starting a new one via findPathInit(), calling freeMemory(), or by destroying the searcher instance.
Aborting or starting a search resets the values returned by .getStepsDone() and .getNodesExpanded() to 0.
*/
// ============================
// ====== COMPILE CONFIG ======
// ============================
// If you want to avoid sqrt() or floats in general, define this.
// Turns out in some testing this was ~12% faster, so it's the default.
#define JPS_NO_FLOAT
// ------------------------------------------------
#include <stddef.h> // for size_t (needed for operator new)
// Assertions
#ifndef JPS_ASSERT
# ifdef _DEBUG
# include <assert.h>
# define JPS_ASSERT(cond) assert(cond)
# else
# define JPS_ASSERT(cond)
# endif
#endif
// The default allocator uses realloc(), free(). Change if necessary.
// You will get the user pointer that you passed to findPath() or the Searcher ctor.
#if !defined(JPS_realloc) || !defined(JPS_free)
# include <stdlib.h> // for realloc, free
# ifndef JPS_realloc
# define JPS_realloc(p, newsize, oldsize, user) realloc(p, newsize)
# endif
# ifndef JPS_free
# define JPS_free(p, oldsize, user) free(p)
# endif
#endif
#ifdef JPS_NO_FLOAT
#define JPS_HEURISTIC_ACCURATE(a, b) (Heuristic::Chebyshev(a, b))
#else
# ifndef JPS_sqrt
// for Euclidean heuristic.
# include <math.h>
# define JPS_sqrt(x) sqrtf(float(x)) // float cast here avoids a warning about implicit int->float cast
# endif
#endif
// Which heuristics to use.
// Basic property: Distance estimate, returns values >= 0. Smaller is better.
// The accurate heuristic should always return guesses less or equal than the estimate heuristic,
// otherwise the resulting paths may not be optimal.
// (The rule of thumb is that the estimate is fast but can overestimate)
// For the implementation of heuristics, scroll down.
#ifndef JPS_HEURISTIC_ACCURATE
#define JPS_HEURISTIC_ACCURATE(a, b) (Heuristic::Euclidean(a, b))
#endif
#ifndef JPS_HEURISTIC_ESTIMATE
#define JPS_HEURISTIC_ESTIMATE(a, b) (Heuristic::Manhattan(a, b))
#endif
// --- Data types ---
namespace JPS {
// unsigned integer type wide enough to store a position on one grid axis.
// Note that on x86, u32 is actually faster than u16.
typedef unsigned PosType;
// Result of heuristics. can also be (unsigned) int but using float by default since that's what sqrtf() returns
// and we don't need to cast float->int that way. Change if you use integer-only heuristics.
// (Euclidean heuristic using sqrt() works fine even if cast to int. Your choice really.)
#ifdef JPS_NO_FLOAT
typedef int ScoreType;
#else
typedef float ScoreType;
#endif
// Size type; used internally for vectors and the like. You can set this to size_t if you want, but 32 bits is more than enough.
typedef unsigned SizeT;
} // end namespace JPS
// ================================
// ====== COMPILE CONFIG END ======
// ================================
// ----------------------------------------------------------------------------------------
typedef unsigned JPS_Flags;
enum JPS_Flags_
{
// No special behavior
JPS_Flag_Default = 0x00,
// If this is defined, disable the greedy direct-short-path check that avoids the large area scanning that JPS does.
// This is just a performance tweak. May save a lot of CPU when constantly re-planning short paths without obstacles
// (e.g. an entity follows close behind another).
// Does not change optimality of results. If you perform your own line-of-sight checks
// before starting a pathfinding run you can disable greedy since checking twice isn't needed,
// but otherwise it's better to leave it enabled.
JPS_Flag_NoGreedy = 0x01,
// If this is set, use standard A* instead of JPS (e.g. if you want to compare performance in your scenario).
// In most cases this will be MUCH slower, but might be beneficial if your grid lookup
// is slow (aka worse than O(1) or more than a few inlined instructions),
// as it avoids the large area scans that the JPS algorithm does.
// (Also increases memory usage as each checked position is expanded into a node.)
JPS_Flag_AStarOnly = 0x02,
// Don't check whether start position is walkable.
// This makes the start position always walkable, even if the map data say otherwise.
JPS_Flag_NoStartCheck = 0x04,
// Don't check whether end position is walkable.
JPS_Flag_NoEndCheck = 0x08,
};
enum JPS_Result
{
JPS_NO_PATH,
JPS_FOUND_PATH,
JPS_NEED_MORE_STEPS,
JPS_EMPTY_PATH,
JPS_OUT_OF_MEMORY
};
// operator new() without #include <new>
// Unfortunately the standard mandates the use of size_t, so we need stddef.h the very least.
// Trick via https://github.com/ocornut/imgui
// "Defining a custom placement new() with a dummy parameter allows us to bypass including <new>
// which on some platforms complains when user has disabled exceptions."
struct JPS__NewDummy {};
inline void* operator new(size_t, JPS__NewDummy, void* ptr) { return ptr; }
inline void operator delete(void*, JPS__NewDummy, void*) {}
#define JPS_PLACEMENT_NEW(p) new(JPS__NewDummy(), p)
namespace JPS {
struct Position
{
PosType x, y;
inline bool operator==(const Position& p) const
{
return x == p.x && y == p.y;
}
inline bool operator!=(const Position& p) const
{
return x != p.x || y != p.y;
}
inline bool isValid() const { return x != PosType(-1); }
};
// The invalid position. Used internally to mark non-walkable points.
static const Position npos = {PosType(-1), PosType(-1)};
static const SizeT noidx = SizeT(-1);
// ctor function to keep Position a real POD struct.
inline static Position Pos(PosType x, PosType y)
{
Position p;
p.x = x;
p.y = y;
return p;
}
template<typename T> inline static T Max(T a, T b) { return a < b ? b : a; }
template<typename T> inline static T Min(T a, T b) { return a < b ? a : b; }
template<typename T> inline static T Abs(T a) { return a < T(0) ? -a : a; }
template<typename T> inline static int Sgn(T val) { return (T(0) < val) - (val < T(0)); }
// Heuristics. Add new ones if you need them.
namespace Heuristic
{
inline ScoreType Manhattan(const Position& a, const Position& b)
{
const int dx = Abs(int(a.x - b.x));
const int dy = Abs(int(a.y - b.y));
return static_cast<ScoreType>(dx + dy);
}
inline ScoreType Chebyshev(const Position& a, const Position& b)
{
const int dx = Abs(int(a.x - b.x));
const int dy = Abs(int(a.y - b.y));
return static_cast<ScoreType>(Max(dx, dy));
}
#ifdef JPS_sqrt
inline ScoreType Euclidean(const Position& a, const Position& b)
{
const int dx = (int(a.x - b.x));
const int dy = (int(a.y - b.y));
return static_cast<ScoreType>(JPS_sqrt(dx*dx + dy*dy));
}
#endif
} // end namespace heuristic
// --- Begin infrastructure, data structures ---
namespace Internal {
// Never allocated outside of a PodVec<Node> --> All nodes are linearly adjacent in memory.
struct Node
{
ScoreType f, g; // heuristic distances
Position pos;
int parentOffs; // no parent if 0
unsigned _flags;
inline int hasParent() const { return parentOffs; }
inline void setOpen() { _flags |= 1; }
inline void setClosed() { _flags |= 2; }
inline unsigned isOpen() const { return _flags & 1; }
inline unsigned isClosed() const { return _flags & 2; }
// We know nodes are allocated sequentially in memory, so this is fine.
inline Node& getParent() { JPS_ASSERT(parentOffs); return this[parentOffs]; }
inline const Node& getParent() const { JPS_ASSERT(parentOffs); return this[parentOffs]; }
inline const Node *getParentOpt() const { return parentOffs ? this + parentOffs : 0; }
inline void setParent(const Node& p) { JPS_ASSERT(&p != this); parentOffs = static_cast<int>(&p - this); }
};
template<typename T>
class PodVec
{
public:
PodVec(void *user = 0)
: _data(0), used(0), cap(0), _user(user)
{}
~PodVec() { dealloc(); }
inline void clear()
{
used = 0;
}
void dealloc()
{
JPS_free(_data, cap * sizeof(T), _user);
_data = 0;
used = 0;
cap = 0;
}
T *alloc()
{
T *e = 0;
if(used < cap || _grow())
{
e = _data + used;
++used;
}
return e;
}
inline void push_back(const T& e)
{
if(T *dst = alloc()) // yes, this silently fails when OOM. this is handled internally.
*dst = e;
}
inline void pop_back() { JPS_ASSERT(used); --used; }
inline T& back() { JPS_ASSERT(used); return _data[used-1]; }
inline SizeT size() const { return used; }
inline bool empty() const { return !used; }
inline T *data() { return _data; }
inline const T *data() const { return _data; }
inline T& operator[](size_t idx) const { JPS_ASSERT(idx < used); return _data[idx]; }
inline SizeT getindex(const T *e) const
{
JPS_ASSERT(e && _data <= e && e < _data + used);
return static_cast<SizeT>(e - _data);
}
void *_reserve(SizeT newcap) // for internal use
{
return cap < newcap ? _grow(newcap) : _data;
}
void resize(SizeT sz)
{
if(_reserve(sz))
used = sz;
}
SizeT _getMemSize() const
{
return cap * sizeof(T);
}
// minimal iterator interface
typedef T* iterator;
typedef const T* const_iterator;
typedef SizeT size_type;
typedef T value_type;
inline iterator begin() { return data(); }
inline iterator end() { return data() + size(); }
inline const_iterator cbegin() const { return data(); }
inline const_iterator cend() const { return data() + size(); }
private:
void *_grow(SizeT newcap)
{
void *p = JPS_realloc(_data, newcap * sizeof(T), cap * sizeof(T), _user);
if(p)
{
_data = (T*)p;
cap = newcap;
}
return p;
}
void * _grow()
{
const SizeT newcap = cap + (cap / 2) + 32;
return _grow(newcap);
}
T *_data;
SizeT used, cap;
public:
void * const _user;
private:
// forbid ops
PodVec<T>& operator=(const PodVec<T>&);
PodVec(const PodVec<T>&);
};
template<typename T>
inline static void Swap(T& a, T& b)
{
const T tmp = a;
a = b;
b = tmp;
}
template<typename IT>
inline static void Reverse(IT first, IT last)
{
while((first != last) && (first != --last))
{
Swap(*first, *last);
++first;
}
}
typedef PodVec<Node> Storage;
class NodeMap
{
private:
static const unsigned LOAD_FACTOR = 8; // estimate: {CPU cache line size (64)} / sizeof(HashLoc)
static const unsigned INITIAL_BUCKETS = 16; // must be > 1 and power of 2
struct HashLoc
{
unsigned hash2; // for early-out check only
SizeT idx; // index in central storage
};
typedef PodVec<HashLoc> Bucket;
// hash function to determine bucket. only uses lower few bits. should jumble lower bits nicely.
static inline unsigned Hash(PosType x, PosType y)
{
return x ^ y;
}
// hash function designed to lose as little data as possible. for early-out checks. all bits used.
static inline unsigned Hash2(PosType x, PosType y)
{
return (y << 16) ^ x;
}
public:
NodeMap(Storage& storage)
: _storageRef(storage), _buckets(storage._user)
{}
~NodeMap()
{
dealloc();
}
void dealloc()
{
for(SizeT i = 0; i < _buckets.size(); ++i)
_buckets[i].~Bucket();
_buckets.dealloc();
}
void clear()
{
// clear the buckets, but *not* the bucket vector
for(SizeT i = 0; i < _buckets.size(); ++i)
_buckets[i].clear();
}
Node *operator()(PosType x, PosType y)
{
const unsigned h = Hash(x, y);
const unsigned h2 = Hash2(x, y);
const SizeT ksz = _buckets.size(); // known to be power-of-2
Bucket *b = 0; // MSVC /W4 complains that this was uninitialized and used, so we init it...
if (ksz)
{
b = &_buckets[h & (ksz - 1)];
const SizeT bsz = b->size();
const HashLoc * const bdata = b->data();
for (SizeT i = 0; i < bsz; ++i)
{
// this is the only place where HashLoc::hash2 is used; it *could*be removed, which means:
// - twice as much space for indexes per cache line
// - but also higher chances for a cache miss because for each entry in the bucket we still need to check the node's X/Y coords,
// and we'll likely end up in a random location in RAM for each node.
// Quick benchmarking showed that *with* the hash2 check it's almost immeasurably (less than 1%) faster.
if (bdata[i].hash2 == h2)
{
Node &n = _storageRef[bdata[i].idx];
if(n.pos.x == x && n.pos.y == y)
return &n;
}
}
}
// enlarge hashmap if necessary; fix bucket if so
SizeT newbsz = _enlarge();
if(newbsz > 1)
b = &_buckets[h & (newbsz - 1)];
else if(newbsz == 1) // error case
return 0;
HashLoc *loc = b->alloc(); // ... see above. b is always initialized here. when ksz==0, _enlarge() will do its initial allocation, so it can never return 0.
if(!loc)
return 0;
loc->hash2 = h2;
loc->idx = _storageRef.size();
// no node at (x, y), create new one
Node *n = _storageRef.alloc();
if(n)
{
n->f = 0;
n->g = 0;
n->pos.x = x;
n->pos.y = y;
n->parentOffs = 0;
n->_flags = 0;
}
return n;
}
SizeT _getMemSize() const
{
SizeT sum = _buckets._getMemSize();
for(Buckets::const_iterator it = _buckets.cbegin(); it != _buckets.cend(); ++it)
sum += it->_getMemSize();
return sum;
}
private:
// return values: 0 = nothing to do; 1 = error; >1: internal storage was enlarged to this many buckets
SizeT _enlarge()
{
const SizeT n = _storageRef.size();
const SizeT oldsz = _buckets.size();
if (n < oldsz * LOAD_FACTOR)
return 0;
// pre-allocate bucket storage that we're going to use
const SizeT newsz = oldsz ? oldsz * 2 : INITIAL_BUCKETS; // stays power of 2
if(!_buckets._reserve(newsz))
return 0; // early out if realloc fails; this not a problem and we can continue.
// forget everything
for(SizeT i = 0; i < oldsz; ++i)
_buckets[i].clear();
// resize and init
for(SizeT i = oldsz; i < newsz; ++i)
{
void *p = _buckets.alloc(); // can't fail since the space was reserved
JPS_PLACEMENT_NEW(p) PodVec<HashLoc>(_buckets._user);
}
const SizeT mask = _buckets.size() - 1;
for(SizeT i = 0; i < n; ++i)
{
const Position p = _storageRef[i].pos;
HashLoc *loc = _buckets[Hash(p.x, p.y) & mask].alloc();
if(!loc)
return 1; // error case
loc->hash2 = Hash2(p.x, p.y);
loc->idx = i;
}
return newsz;
}
Storage& _storageRef;
typedef PodVec<Bucket> Buckets;
Buckets _buckets;
};
class OpenList
{
private:
const Storage& _storageRef;
PodVec<SizeT> idxHeap;
public:
OpenList(const Storage& storage)
: _storageRef(storage), idxHeap(storage._user)
{}
inline void pushNode(Node *n)
{
_heapPushIdx(_storageRef.getindex(n));
}
inline Node& popNode()
{
return _storageRef[_popIdx()];
}
// re-heapify after node changed its order
inline void fixNode(const Node& n)
{
const unsigned ni = _storageRef.getindex(&n);
const unsigned sz = idxHeap.size();
unsigned *p = idxHeap.data();
for(unsigned i = 0; i < sz; ++i) // TODO: if this ever becomes a perf bottleneck: make it so that each node knows its heap index
if(p[i] == ni)
{
_fixIdx(i);
return;
}
JPS_ASSERT(false); // expect node to be found
}
inline void dealloc() { idxHeap.dealloc(); }
inline void clear() { idxHeap.clear(); }
inline bool empty() const { return idxHeap.empty(); }
inline SizeT _getMemSize() const
{
return idxHeap._getMemSize();
}
private:
inline bool _heapLess(SizeT a, SizeT b)
{
return _storageRef[idxHeap[a]].f > _storageRef[idxHeap[b]].f;
}
inline bool _heapLessIdx(SizeT a, SizeT idx)
{
return _storageRef[idxHeap[a]].f > _storageRef[idx].f;
}
void _percolateUp(SizeT i)
{
const SizeT idx = idxHeap[i];
SizeT p;
goto start;
do
{
idxHeap[i] = idxHeap[p]; // parent is smaller, move it down
i = p; // continue with parent
start:
p = (i - 1) >> 1;
}
while(i && _heapLessIdx(p, idx));
idxHeap[i] = idx; // found correct place for idx
}
void _percolateDown(SizeT i)
{
const SizeT idx = idxHeap[i];
const SizeT sz = idxHeap.size();
SizeT child;
goto start;
do
{
// pick right sibling if exists and larger or equal
if(child + 1 < sz && !_heapLess(child+1, child))
++child;
idxHeap[i] = idxHeap[child];
i = child;
start:
child = (i << 1) + 1;
}
while(child < sz);
idxHeap[i] = idx;
_percolateUp(i);
}
void _heapPushIdx(SizeT idx)
{
SizeT i = idxHeap.size();
idxHeap.push_back(idx);
_percolateUp(i);
}
SizeT _popIdx()
{
SizeT sz = idxHeap.size();
JPS_ASSERT(sz);
const SizeT root = idxHeap[0];
idxHeap[0] = idxHeap[--sz];
idxHeap.pop_back();
if(sz > 1)
_percolateDown(0);
return root;
}
// re-heapify node at index i
inline void _fixIdx(SizeT i)
{
_percolateDown(i);
_percolateUp(i);
}
};
#undef JPS_PLACEMENT_NEW
// --- End infrastructure, data structures ---
// All those things that don't depend on template parameters...
class SearcherBase
{
protected:
Storage storage;
OpenList open;
NodeMap nodemap;
Position endPos;
SizeT endNodeIdx;
JPS_Flags flags;
int stepsRemain;
SizeT stepsDone;
SearcherBase(void *user)
: storage(user)
, open(storage)
, nodemap(storage)
, endPos(npos), endNodeIdx(noidx)
, flags(0)
, stepsRemain(0), stepsDone(0)
{}
void clear()
{
open.clear();
nodemap.clear();
storage.clear();
endNodeIdx = noidx;
stepsDone = 0;
}
void _expandNode(const Position jp, Node& jn, const Node& parent)
{
JPS_ASSERT(jn.pos == jp);
ScoreType extraG = JPS_HEURISTIC_ACCURATE(jp, parent.pos);
ScoreType newG = parent.g + extraG;
if(!jn.isOpen() || newG < jn.g)
{
jn.g = newG;
jn.f = jn.g + JPS_HEURISTIC_ESTIMATE(jp, endPos);
jn.setParent(parent);
if(!jn.isOpen())
{
open.pushNode(&jn);
jn.setOpen();
}
else
open.fixNode(jn);
}
}
public:
template <typename PV>
JPS_Result generatePath(PV& path, unsigned step) const;
void freeMemory()
{
open.dealloc();
nodemap.dealloc();
storage.dealloc();
endNodeIdx = noidx;
}
// --- Statistics ---
inline SizeT getStepsDone() const { return stepsDone; }
inline SizeT getNodesExpanded() const { return storage.size(); }
SizeT getTotalMemoryInUse() const
{
return storage._getMemSize()
+ nodemap._getMemSize()
+ open._getMemSize();
}
};
template <typename GRID> class Searcher : public SearcherBase
{
public:
Searcher(const GRID& g, void *user = 0)
: SearcherBase(user), grid(g)
{}
// single-call
template<typename PV>
bool findPath(PV& path, Position start, Position end, unsigned step, JPS_Flags flags = JPS_Flag_Default);
// incremental pathfinding
JPS_Result findPathInit(Position start, Position end, JPS_Flags flags = JPS_Flag_Default);
JPS_Result findPathStep(int limit);
// generate path after one was found
template<typename PV>
JPS_Result findPathFinish(PV& path, unsigned step) const;
private:
const GRID& grid;
Node *getNode(const Position& pos);
bool identifySuccessors(const Node& n);
bool findPathGreedy(Node *start, Node *end);
unsigned findNeighborsAStar(const Node& n, Position *wptr);
unsigned findNeighborsJPS(const Node& n, Position *wptr) const;
Position jumpP(const Position& p, const Position& src);
Position jumpD(Position p, int dx, int dy);
Position jumpX(Position p, int dx);
Position jumpY(Position p, int dy);
// forbid any ops
Searcher& operator=(const Searcher<GRID>&);
Searcher(const Searcher<GRID>&);
};
// -----------------------------------------------------------------------
template<typename PV> JPS_Result SearcherBase::generatePath(PV& path, unsigned step) const
{
if(endNodeIdx == noidx)
return JPS_NO_PATH;
const SizeT offset = path.size();
SizeT added = 0;
const Node& endNode = storage[endNodeIdx];
const Node *next = &endNode;
if(!next->hasParent())
return JPS_NO_PATH;
if(step)
{
const Node *prev = endNode.getParentOpt();
if(!prev)
return JPS_NO_PATH;
do
{
const unsigned x = next->pos.x, y = next->pos.y;
int dx = int(prev->pos.x - x);
int dy = int(prev->pos.y - y);
const int adx = Abs(dx);
const int ady = Abs(dy);
JPS_ASSERT(!dx || !dy || adx == ady); // known to be straight, if diagonal
const int steps = Max(adx, ady);
dx = int(step) * Sgn(dx);
dy = int(step) * Sgn(dy);
int dxa = 0, dya = 0;
for(int i = 0; i < steps; i += step)
{
path.push_back(Pos(x+dxa, y+dya));
++added;
dxa += dx;
dya += dy;
}
next = prev;
prev = prev->getParentOpt();
}
while (prev);
}
else
{
do
{
JPS_ASSERT(next != &next->getParent());
path.push_back(next->pos);
++added;
next = &next->getParent();
}
while (next->hasParent());
}
// JPS::PathVector silently discards push_back() when memory allocation fails;
// detect that case and roll back.
if(path.size() != offset + added)
{
path.resize(offset);
return JPS_OUT_OF_MEMORY;
}
// Nodes were traversed backwards, fix that
Reverse(path.begin() + offset, path.end());
return JPS_FOUND_PATH;
}
//-----------------------------------------
template <typename GRID> inline Node *Searcher<GRID>::getNode(const Position& pos)
{
JPS_ASSERT(grid(pos.x, pos.y));
return nodemap(pos.x, pos.y);
}
template <typename GRID> Position Searcher<GRID>::jumpP(const Position &p, const Position& src)
{
JPS_ASSERT(grid(p.x, p.y));