1 /*
2  * Copyright (c) 2007-2013 Scott Lembcke and Howling Moon Software
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 module dchip.cpSpaceHash;
23 
24 import dchip.cpBB;
25 import dchip.chipmunk;
26 import dchip.chipmunk_private;
27 import dchip.chipmunk_types;
28 import dchip.cpArray;
29 import dchip.cpHashSet;
30 import dchip.prime;
31 import dchip.cpSpatialIndex;
32 import dchip.chipmunk_types;
33 import dchip.cpVect;
34 import dchip.util;
35 
36 struct cpSpaceHash
37 {
38     cpSpatialIndex spatialIndex;
39 
40     int numcells;
41     cpFloat celldim = 0;
42 
43     cpSpaceHashBin** table;
44     cpHashSet* handleSet;
45 
46     cpSpaceHashBin* pooledBins;
47     cpArray* pooledHandles;
48     cpArray* allocatedBuffers;
49 
50     cpTimestamp stamp;
51 }
52 
53 struct cpHandle
54 {
55     void* obj;
56     int retain;
57     cpTimestamp stamp;
58 }
59 
60 cpHandle* cpHandleInit(cpHandle* hand, void* obj)
61 {
62     hand.obj    = obj;
63     hand.retain = 0;
64     hand.stamp  = 0;
65 
66     return hand;
67 }
68 
69 void cpHandleRetain(cpHandle* hand)
70 {
71     hand.retain++;
72 }
73 
74 void cpHandleRelease(cpHandle* hand, cpArray* pooledHandles)
75 {
76     hand.retain--;
77 
78     if (hand.retain == 0)
79         cpArrayPush(pooledHandles, hand);
80 }
81 
82 int handleSetEql(void* obj, cpHandle* hand)
83 {
84     return (obj == hand.obj);
85 }
86 
87 void* handleSetTrans(void* obj, cpSpaceHash* hash)
88 {
89     if (hash.pooledHandles.num == 0)
90     {
91         // handle pool is exhausted, make more
92         int count = CP_BUFFER_BYTES / cpHandle.sizeof;
93         cpAssertHard(count, "Internal Error: Buffer size is too small.");
94 
95         cpHandle* buffer = cast(cpHandle*)cpcalloc(1, CP_BUFFER_BYTES);
96         cpArrayPush(hash.allocatedBuffers, buffer);
97 
98         for (int i = 0; i < count; i++)
99             cpArrayPush(hash.pooledHandles, buffer + i);
100     }
101 
102     cpHandle* hand = cpHandleInit(cast(cpHandle*)cpArrayPop(hash.pooledHandles), obj);
103     cpHandleRetain(hand);
104 
105     return hand;
106 }
107 
108 struct cpSpaceHashBin
109 {
110     cpHandle* handle;
111     cpSpaceHashBin* next;
112 }
113 
114 void recycleBin(cpSpaceHash* hash, cpSpaceHashBin* bin)
115 {
116     bin.next        = hash.pooledBins;
117     hash.pooledBins = bin;
118 }
119 
120 void clearTableCell(cpSpaceHash* hash, int idx)
121 {
122     cpSpaceHashBin* bin = hash.table[idx];
123 
124     while (bin)
125     {
126         cpSpaceHashBin* next = bin.next;
127 
128         cpHandleRelease(bin.handle, hash.pooledHandles);
129         recycleBin(hash, bin);
130 
131         bin = next;
132     }
133 
134     hash.table[idx] = null;
135 }
136 
137 void clearTable(cpSpaceHash* hash)
138 {
139     for (int i = 0; i < hash.numcells; i++)
140         clearTableCell(hash, i);
141 }
142 
143 // Get a recycled or new bin.
144 cpSpaceHashBin* getEmptyBin(cpSpaceHash* hash)
145 {
146     cpSpaceHashBin* bin = hash.pooledBins;
147 
148     if (bin)
149     {
150         hash.pooledBins = bin.next;
151         return bin;
152     }
153     else
154     {
155         // Pool is exhausted, make more
156         int count = CP_BUFFER_BYTES / cpSpaceHashBin.sizeof;
157         cpAssertHard(count, "Internal Error: Buffer size is too small.");
158 
159         cpSpaceHashBin* buffer = cast(cpSpaceHashBin*)cpcalloc(1, CP_BUFFER_BYTES);
160         cpArrayPush(hash.allocatedBuffers, buffer);
161 
162         // push all but the first one, return the first instead
163         for (int i = 1; i < count; i++)
164             recycleBin(hash, buffer + i);
165 
166         return buffer;
167     }
168 }
169 
170 cpSpaceHash* cpSpaceHashAlloc()
171 {
172     return cast(cpSpaceHash*)cpcalloc(1, cpSpaceHash.sizeof);
173 }
174 
175 // Frees the old table, and allocate a new one.
176 void cpSpaceHashAllocTable(cpSpaceHash* hash, int numcells)
177 {
178     cpfree(hash.table);
179 
180     hash.numcells = numcells;
181     hash.table    = cast(cpSpaceHashBin**)cpcalloc(numcells, (cpSpaceHashBin*).sizeof);
182 }
183 
184 cpSpatialIndex* cpSpaceHashInit(cpSpaceHash* hash, cpFloat celldim, int numcells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex* staticIndex)
185 {
186     cpSpatialIndexInit(cast(cpSpatialIndex*)hash, Klass(), bbfunc, staticIndex);
187 
188     cpSpaceHashAllocTable(hash, next_prime(numcells));
189     hash.celldim = celldim;
190 
191     hash.handleSet = cpHashSetNew(0, cast(cpHashSetEqlFunc)&handleSetEql);
192 
193     hash.pooledHandles = cpArrayNew(0);
194 
195     hash.pooledBins       = null;
196     hash.allocatedBuffers = cpArrayNew(0);
197 
198     hash.stamp = 1;
199 
200     return cast(cpSpatialIndex*)hash;
201 }
202 
203 cpSpatialIndex* cpSpaceHashNew(cpFloat celldim, int cells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex* staticIndex)
204 {
205     return cpSpaceHashInit(cpSpaceHashAlloc(), celldim, cells, bbfunc, staticIndex);
206 }
207 
208 void cpSpaceHashDestroy(cpSpaceHash* hash)
209 {
210     if (hash.table)
211         clearTable(hash);
212     cpfree(hash.table);
213 
214     cpHashSetFree(hash.handleSet);
215 
216     cpArrayFreeEach(hash.allocatedBuffers, &cpfree);
217     cpArrayFree(hash.allocatedBuffers);
218     cpArrayFree(hash.pooledHandles);
219 }
220 
221 cpBool containsHandle(cpSpaceHashBin* bin, cpHandle* hand)
222 {
223     while (bin)
224     {
225         if (bin.handle == hand)
226             return cpTrue;
227         bin = bin.next;
228     }
229 
230     return cpFalse;
231 }
232 
233 // The hash function itself.
234 cpHashValue hash_func(cpHashValue x, cpHashValue y, cpHashValue n)
235 {
236     return (x * 1640531513uL ^ y * 2654435789uL) % n;
237 }
238 
239 // Much faster than (int)floor(f)
240 // Profiling showed floor() to be a sizable performance hog
241 int floor_int(cpFloat f)
242 {
243     int i = cast(int)f;
244     return (f < 0.0f && f != i ? i - 1 : i);
245 }
246 
247 void hashHandle(cpSpaceHash* hash, cpHandle* hand, cpBB bb)
248 {
249     // Find the dimensions in cell coordinates.
250     cpFloat dim = hash.celldim;
251     int l       = floor_int(bb.l / dim); // Fix by ShiftZ
252     int r       = floor_int(bb.r / dim);
253     int b       = floor_int(bb.b / dim);
254     int t       = floor_int(bb.t / dim);
255 
256     int n = hash.numcells;
257 
258     for (int i = l; i <= r; i++)
259     {
260         for (int j = b; j <= t; j++)
261         {
262             cpHashValue idx     = hash_func(i, j, n);
263             cpSpaceHashBin* bin = hash.table[idx];
264 
265             // Don't add an object twice to the same cell.
266             if (containsHandle(bin, hand))
267                 continue;
268 
269             cpHandleRetain(hand);
270 
271             // Insert a new bin for the handle in this cell.
272             cpSpaceHashBin* newBin = getEmptyBin(hash);
273             newBin.handle   = hand;
274             newBin.next     = bin;
275             hash.table[idx] = newBin;
276         }
277     }
278 }
279 
280 void cpSpaceHashInsert(cpSpaceHash* hash, void* obj, cpHashValue hashid)
281 {
282     cpHandle* hand = cast(cpHandle*)cpHashSetInsert(hash.handleSet, hashid, obj, hash, cast(cpHashSetTransFunc)&handleSetTrans);
283     hashHandle(hash, hand, hash.spatialIndex.bbfunc(obj));
284 }
285 
286 void cpSpaceHashRehashObject(cpSpaceHash* hash, void* obj, cpHashValue hashid)
287 {
288     cpHandle* hand = cast(cpHandle*)cpHashSetRemove(hash.handleSet, hashid, obj);
289 
290     if (hand)
291     {
292         hand.obj = null;
293         cpHandleRelease(hand, hash.pooledHandles);
294 
295         cpSpaceHashInsert(hash, obj, hashid);
296     }
297 }
298 
299 void rehash_helper(cpHandle* hand, cpSpaceHash* hash)
300 {
301     hashHandle(hash, hand, hash.spatialIndex.bbfunc(hand.obj));
302 }
303 
304 void cpSpaceHashRehash(cpSpaceHash* hash)
305 {
306     clearTable(hash);
307     cpHashSetEach(hash.handleSet, safeCast!cpHashSetIteratorFunc(&rehash_helper), hash);
308 }
309 
310 void cpSpaceHashRemove(cpSpaceHash* hash, void* obj, cpHashValue hashid)
311 {
312     cpHandle* hand = cast(cpHandle*)cpHashSetRemove(hash.handleSet, hashid, obj);
313 
314     if (hand)
315     {
316         hand.obj = null;
317         cpHandleRelease(hand, hash.pooledHandles);
318     }
319 }
320 
321 struct eachContext
322 {
323     cpSpatialIndexIteratorFunc func;
324     void* data;
325 }
326 
327 void eachHelper(cpHandle* hand, eachContext* context)
328 {
329     context.func(hand.obj, context.data);
330 }
331 
332 void cpSpaceHashEach(cpSpaceHash* hash, cpSpatialIndexIteratorFunc func, void* data)
333 {
334     eachContext context = { func, data };
335     cpHashSetEach(hash.handleSet, safeCast!cpHashSetIteratorFunc(&eachHelper), &context);
336 }
337 
338 void remove_orphaned_handles(cpSpaceHash* hash, cpSpaceHashBin** bin_ptr)
339 {
340     cpSpaceHashBin* bin = *bin_ptr;
341 
342     while (bin)
343     {
344         cpHandle* hand       = bin.handle;
345         cpSpaceHashBin* next = bin.next;
346 
347         if (!hand.obj)
348         {
349             // orphaned handle, unlink and recycle the bin
350             (*bin_ptr) = bin.next;
351             recycleBin(hash, bin);
352 
353             cpHandleRelease(hand, hash.pooledHandles);
354         }
355         else
356         {
357             bin_ptr = &bin.next;
358         }
359 
360         bin = next;
361     }
362 }
363 
364 void query_helper(cpSpaceHash* hash, cpSpaceHashBin** bin_ptr, void* obj, cpSpatialIndexQueryFunc func, void* data)
365 {
366 restart:
367 
368     for (cpSpaceHashBin * bin = *bin_ptr; bin; bin = bin.next)
369     {
370         cpHandle* hand = bin.handle;
371         void* other    = hand.obj;
372 
373         if (hand.stamp == hash.stamp || obj == other)
374         {
375             continue;
376         }
377         else if (other)
378         {
379             func(obj, other, 0, data);
380             hand.stamp = hash.stamp;
381         }
382         else
383         {
384             // The object for this handle has been removed
385             // cleanup this cell and restart the query
386             remove_orphaned_handles(hash, bin_ptr);
387             goto restart;             // GCC not smart enough/able to tail call an inlined function.
388         }
389     }
390 }
391 
392 void cpSpaceHashQuery(cpSpaceHash* hash, void* obj, cpBB bb, cpSpatialIndexQueryFunc func, void* data)
393 {
394     // Get the dimensions in cell coordinates.
395     cpFloat dim = hash.celldim;
396     int l       = floor_int(bb.l / dim); // Fix by ShiftZ
397     int r       = floor_int(bb.r / dim);
398     int b       = floor_int(bb.b / dim);
399     int t       = floor_int(bb.t / dim);
400 
401     int n = hash.numcells;
402     cpSpaceHashBin** table = hash.table;
403 
404     // Iterate over the cells and query them.
405     for (int i = l; i <= r; i++)
406     {
407         for (int j = b; j <= t; j++)
408         {
409             query_helper(hash, &table[hash_func(i, j, n)], obj, func, data);
410         }
411     }
412 
413     hash.stamp++;
414 }
415 
416 // Similar to struct eachPair above.
417 struct queryRehashContext
418 {
419     cpSpaceHash* hash;
420     cpSpatialIndexQueryFunc func;
421     void* data;
422 }
423 
424 // Hashset iterator func used with cpSpaceHashQueryRehash().
425 void queryRehash_helper(cpHandle* hand, queryRehashContext* context)
426 {
427     cpSpaceHash* hash = context.hash;
428     cpSpatialIndexQueryFunc func = context.func;
429     void* data = context.data;
430 
431     cpFloat dim = hash.celldim;
432     int n       = hash.numcells;
433 
434     void* obj = hand.obj;
435     cpBB  bb  = hash.spatialIndex.bbfunc(obj);
436 
437     int l = floor_int(bb.l / dim);
438     int r = floor_int(bb.r / dim);
439     int b = floor_int(bb.b / dim);
440     int t = floor_int(bb.t / dim);
441 
442     cpSpaceHashBin** table = hash.table;
443 
444     for (int i = l; i <= r; i++)
445     {
446         for (int j = b; j <= t; j++)
447         {
448             cpHashValue idx     = hash_func(i, j, n);
449             cpSpaceHashBin* bin = table[idx];
450 
451             if (containsHandle(bin, hand))
452                 continue;
453 
454             cpHandleRetain(hand);             // this MUST be done first in case the object is removed in func()
455             query_helper(hash, &bin, obj, func, data);
456 
457             cpSpaceHashBin* newBin = getEmptyBin(hash);
458             newBin.handle = hand;
459             newBin.next   = bin;
460             table[idx]     = newBin;
461         }
462     }
463 
464     // Increment the stamp for each object hashed.
465     hash.stamp++;
466 }
467 
468 void cpSpaceHashReindexQuery(cpSpaceHash* hash, cpSpatialIndexQueryFunc func, void* data)
469 {
470     clearTable(hash);
471 
472     queryRehashContext context = { hash, func, data };
473     cpHashSetEach(hash.handleSet, safeCast!cpHashSetIteratorFunc(&queryRehash_helper), &context);
474 
475     cpSpatialIndexCollideStatic(cast(cpSpatialIndex*)hash, hash.spatialIndex.staticIndex, func, data);
476 }
477 
478 cpFloat segmentQuery_helper(cpSpaceHash* hash, cpSpaceHashBin** bin_ptr, void* obj, cpSpatialIndexSegmentQueryFunc func, void* data)
479 {
480     cpFloat t = 1.0f;
481 
482 restart:
483 
484     for (cpSpaceHashBin * bin = *bin_ptr; bin; bin = bin.next)
485     {
486         cpHandle* hand = bin.handle;
487         void* other    = hand.obj;
488 
489         // Skip over certain conditions
490         if (hand.stamp == hash.stamp)
491         {
492             continue;
493         }
494         else if (other)
495         {
496             t = cpfmin(t, func(obj, other, data));
497             hand.stamp = hash.stamp;
498         }
499         else
500         {
501             // The object for this handle has been removed
502             // cleanup this cell and restart the query
503             remove_orphaned_handles(hash, bin_ptr);
504             goto restart;             // GCC not smart enough/able to tail call an inlined function.
505         }
506     }
507 
508     return t;
509 }
510 
511 // modified from http://playtechs.blogspot.com/2007/03/raytracing-on-grid.html
512 void cpSpaceHashSegmentQuery(cpSpaceHash* hash, void* obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void* data)
513 {
514     a = cpvmult(a, 1.0f / hash.celldim);
515     b = cpvmult(b, 1.0f / hash.celldim);
516 
517     int cell_x = floor_int(a.x), cell_y = floor_int(a.y);
518 
519     cpFloat t = 0;
520 
521     int x_inc, y_inc;
522     cpFloat temp_v = 0, temp_h = 0;
523 
524     if (b.x > a.x)
525     {
526         x_inc  = 1;
527         temp_h = (cpffloor(a.x + 1.0f) - a.x);
528     }
529     else
530     {
531         x_inc  = -1;
532         temp_h = (a.x - cpffloor(a.x));
533     }
534 
535     if (b.y > a.y)
536     {
537         y_inc  = 1;
538         temp_v = (cpffloor(a.y + 1.0f) - a.y);
539     }
540     else
541     {
542         y_inc  = -1;
543         temp_v = (a.y - cpffloor(a.y));
544     }
545 
546     // Division by zero is *very* slow on ARM
547     cpFloat dx    = cpfabs(b.x - a.x), dy = cpfabs(b.y - a.y);
548     cpFloat dt_dx = (dx ? 1.0f / dx : INFINITY), dt_dy = (dy ? 1.0f / dy : INFINITY);
549 
550     // fix NANs in horizontal directions
551     cpFloat next_h = (temp_h ? temp_h * dt_dx : dt_dx);
552     cpFloat next_v = (temp_v ? temp_v * dt_dy : dt_dy);
553 
554     int n = hash.numcells;
555     cpSpaceHashBin** table = hash.table;
556 
557     while (t < t_exit)
558     {
559         cpHashValue idx = hash_func(cell_x, cell_y, n);
560         t_exit = cpfmin(t_exit, segmentQuery_helper(hash, &table[idx], obj, func, data));
561 
562         if (next_v < next_h)
563         {
564             cell_y += y_inc;
565             t       = next_v;
566             next_v += dt_dy;
567         }
568         else
569         {
570             cell_x += x_inc;
571             t       = next_h;
572             next_h += dt_dx;
573         }
574     }
575 
576     hash.stamp++;
577 }
578 
579 void cpSpaceHashResize(cpSpaceHash* hash, cpFloat celldim, int numcells)
580 {
581     if (hash.spatialIndex.klass != Klass())
582     {
583         cpAssertWarn(cpFalse, "Ignoring cpSpaceHashResize() call to non-cpSpaceHash spatial index.");
584         return;
585     }
586 
587     clearTable(hash);
588 
589     hash.celldim = celldim;
590     cpSpaceHashAllocTable(hash, next_prime(numcells));
591 }
592 
593 int cpSpaceHashCount(cpSpaceHash* hash)
594 {
595     return cpHashSetCount(hash.handleSet);
596 }
597 
598 int cpSpaceHashContains(cpSpaceHash* hash, void* obj, cpHashValue hashid)
599 {
600     return cpHashSetFind(hash.handleSet, hashid, obj) != null;
601 }
602 
603 __gshared cpSpatialIndexClass klass;
604 
605 void _initModuleCtor_cpSpaceHash()
606 {
607     klass = cpSpatialIndexClass(
608         cast(cpSpatialIndexDestroyImpl)&cpSpaceHashDestroy,
609 
610         cast(cpSpatialIndexCountImpl)&cpSpaceHashCount,
611         cast(cpSpatialIndexEachImpl)&cpSpaceHashEach,
612         cast(cpSpatialIndexContainsImpl)&cpSpaceHashContains,
613 
614         cast(cpSpatialIndexInsertImpl)&cpSpaceHashInsert,
615         cast(cpSpatialIndexRemoveImpl)&cpSpaceHashRemove,
616 
617         cast(cpSpatialIndexReindexImpl)&cpSpaceHashRehash,
618         cast(cpSpatialIndexReindexObjectImpl)&cpSpaceHashRehashObject,
619         cast(cpSpatialIndexReindexQueryImpl)&cpSpaceHashReindexQuery,
620 
621         cast(cpSpatialIndexQueryImpl)&cpSpaceHashQuery,
622         cast(cpSpatialIndexSegmentQueryImpl)&cpSpaceHashSegmentQuery,
623     );
624 }
625 
626 cpSpatialIndexClass* Klass()
627 {
628     return &klass;
629 }
630 
631 // drey later todo
632 // version = CHIP_BBTREE_DEBUG_DRAW;
633 version (CHIP_BBTREE_DEBUG_DRAW)
634 {
635     /+ #include "OpenGL/gl.h"
636     #include "OpenGL/glu.h"
637     #include <GLUT/glut.h>
638 
639     void cpSpaceHashRenderDebug(cpSpatialIndex* index)
640     {
641         if (index.klass != &klass)
642         {
643             cpAssertWarn(cpFalse, "Ignoring cpSpaceHashRenderDebug() call to non-spatial hash spatial index.");
644             return;
645         }
646 
647         cpSpaceHash* hash = (cpSpaceHash*)index;
648         cpBB bb = cpBBNew(-320, -240, 320, 240);
649 
650         cpFloat dim = hash.celldim;
651         int n       = hash.numcells;
652 
653         int l = (int)floor(bb.l / dim);
654         int r = (int)floor(bb.r / dim);
655         int b = (int)floor(bb.b / dim);
656         int t = (int)floor(bb.t / dim);
657 
658         for (int i = l; i <= r; i++)
659         {
660             for (int j = b; j <= t; j++)
661             {
662                 int cell_count = 0;
663 
664                 int index = hash_func(i, j, n);
665 
666                 for (cpSpaceHashBin* bin = hash.table[index]; bin; bin = bin.next)
667                     cell_count++;
668 
669                 GLfloat v = 1.0f - (GLfloat)cell_count / 10.0f;
670                 glColor3f(v, v, v);
671                 glRectf(i * dim, j * dim, (i + 1) * dim, (j + 1) * dim);
672             }
673         }
674     } +/
675 }