comparison trunk/chipmunkd/cpSpaceHash.d @ 4:7ebbd4d05553

initial commit
author Extrawurst
date Thu, 02 Dec 2010 02:11:26 +0100
parents
children df4ebc8add66
comparison
equal deleted inserted replaced
3:81145a61c2fe 4:7ebbd4d05553
1
2 // written in the D programming language
3
4 module chipmunkd.cpSpaceHash;
5
6 import chipmunkd.chipmunk;
7 import chipmunkd.chipmunk_types_h;
8 import chipmunkd.cpVect,chipmunkd.cpVect_h;
9 import chipmunkd.cpBB;
10 import chipmunkd.cpHashSet;
11 import chipmunkd.cpArray;
12 import chipmunkd.prime;
13
14 // The spatial hash is Chipmunk's default (and currently only) spatial index type.
15 // Based on a chained hash table.
16
17 // Used internally to track objects added to the hash
18 struct cpHandle{
19 // Pointer to the object
20 void *obj;
21 // Retain count
22 int retain;
23 // Query stamp. Used to make sure two objects
24 // aren't identified twice in the same query.
25 cpTimestamp stamp;
26 }
27
28 // Linked list element for in the chains.
29 struct cpSpaceHashBin{
30 cpHandle *handle;
31 cpSpaceHashBin *next;
32 }
33
34 // BBox callback. Called whenever the hash needs a bounding box from an object.
35 alias cpBB function(void *obj)cpSpaceHashBBFunc;
36
37 struct cpSpaceHash{
38 // Number of cells in the table.
39 int numcells;
40 // Dimentions of the cells.
41 cpFloat celldim;
42
43 // BBox callback.
44 cpSpaceHashBBFunc bbfunc;
45
46 // Hashset of the handles and the recycled ones.
47 cpHashSet *handleSet;
48 cpArray *pooledHandles;
49
50 // The table and the recycled bins.
51 cpSpaceHashBin **table;
52 cpSpaceHashBin *pooledBins;
53
54 // list of buffers to free on destruction.
55 cpArray *allocatedBuffers;
56
57 // Incremented on each query. See cpHandle.stamp.
58 cpTimestamp stamp;
59 }
60
61 ////Basic allocation/destruction functions.
62 //cpSpaceHash *cpSpaceHashAlloc(void);
63 //cpSpaceHash *cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc);
64 //cpSpaceHash *cpSpaceHashNew(cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc);
65 //
66 //void cpSpaceHashDestroy(cpSpaceHash *hash);
67 //void cpSpaceHashFree(cpSpaceHash *hash);
68 //
69 //// Resize the hashtable. (Does not rehash! You must call cpSpaceHashRehash() if needed.)
70 //void cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells);
71 //
72 //// Add an object to the hash.
73 //void cpSpaceHashInsert(cpSpaceHash *hash, void *obj, cpHashValue id, cpBB _deprecated_ignored);
74 //// Remove an object from the hash.
75 //void cpSpaceHashRemove(cpSpaceHash *hash, void *obj, cpHashValue id);
76 //
77 //// Iterator function
78 alias void function(void *obj, void *data)cpSpaceHashIterator;
79 //// Iterate over the objects in the hash.
80 //void cpSpaceHashEach(cpSpaceHash *hash, cpSpaceHashIterator func, void *data);
81 //
82 //// Rehash the contents of the hash.
83 //void cpSpaceHashRehash(cpSpaceHash *hash);
84 //// Rehash only a specific object.
85 //void cpSpaceHashRehashObject(cpSpaceHash *hash, void *obj, cpHashValue id);
86 //
87 //// Query callback.
88 alias void function(void *obj1, void *obj2, void *data)cpSpaceHashQueryFunc;
89 //// Point query the hash. A reference to the query point is passed as obj1 to the query callback.
90 //void cpSpaceHashPointQuery(cpSpaceHash *hash, cpVect point, cpSpaceHashQueryFunc func, void *data);
91 //// Query the hash for a given BBox.
92 //void cpSpaceHashQuery(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data);
93 //// Run a query for the object, then insert it. (Optimized case)
94 //void cpSpaceHashQueryInsert(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data);
95 //// Rehashes while querying for each object. (Optimized case)
96 //void cpSpaceHashQueryRehash(cpSpaceHash *hash, cpSpaceHashQueryFunc func, void *data);
97 //
98 //// Segment Query callback.
99 //// Return value is uesd for early exits of the query.
100 //// If while traversing the grid, the raytrace function detects that an entire grid cell is beyond the hit point, it will stop the trace.
101 alias cpFloat function(void *obj1, void *obj2, void *data)cpSpaceHashSegmentQueryFunc;
102 //void cpSpaceHashSegmentQuery(cpSpaceHash *hash, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpaceHashSegmentQueryFunc func, void *data);
103 //
104
105 static cpHandle*
106 cpHandleInit(cpHandle *hand, void *obj)
107 {
108 hand.obj = obj;
109 hand.retain = 0;
110 hand.stamp = 0;
111
112 return hand;
113 }
114
115 static void cpHandleRetain(cpHandle *hand){hand.retain++;}
116
117 static void
118 cpHandleRelease(cpHandle *hand, cpArray *pooledHandles)
119 {
120 hand.retain--;
121 if(hand.retain == 0) cpArrayPush(pooledHandles, hand);
122 }
123
124 cpSpaceHash*
125 cpSpaceHashAlloc()
126 {
127 return cast(cpSpaceHash *)cpcalloc(1, cpSpaceHash.sizeof);
128 }
129
130 // Frees the old table, and allocate a new one.
131 static void
132 cpSpaceHashAllocTable(cpSpaceHash *hash, int numcells)
133 {
134 cpfree(hash.table);
135
136 hash.numcells = numcells;
137 hash.table = cast(cpSpaceHashBin **)cpcalloc(numcells, (cpSpaceHashBin *).sizeof);
138 }
139
140 // Equality function for the handleset.
141 static int handleSetEql(void *obj, cpHandle* hand){return (obj == hand.obj);}
142
143 // Transformation function for the handleset.
144 static void *
145 handleSetTrans(void *obj, cpSpaceHash *hash)
146 {
147 if(hash.pooledHandles.num == 0){
148 // handle pool is exhausted, make more
149 int count = CP_BUFFER_BYTES/cpHandle.sizeof;
150 assert(count, "Buffer size is too small.");
151
152 cpHandle *buffer = cast(cpHandle *)cpmalloc(CP_BUFFER_BYTES);
153 cpArrayPush(hash.allocatedBuffers, buffer);
154
155 for(int i=0; i<count; i++) cpArrayPush(hash.pooledHandles, buffer + i);
156 }
157
158 cpHandle *hand = cpHandleInit(cast(cpHandle *) cpArrayPop(hash.pooledHandles), obj);
159 cpHandleRetain(hand);
160
161 return hand;
162 }
163
164 cpSpaceHash*
165 cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int numcells, cpSpaceHashBBFunc bbfunc)
166 {
167 cpSpaceHashAllocTable(hash, next_prime(numcells));
168 hash.celldim = celldim;
169 hash.bbfunc = bbfunc;
170
171 hash.handleSet = cpHashSetNew(0, cast(cpHashSetEqlFunc)&handleSetEql, cast(cpHashSetTransFunc)&handleSetTrans);
172 hash.pooledHandles = cpArrayNew(0);
173
174 hash.pooledBins = null;
175 hash.allocatedBuffers = cpArrayNew(0);
176
177 hash.stamp = 1;
178
179 return hash;
180 }
181
182 cpSpaceHash*
183 cpSpaceHashNew(cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc)
184 {
185 return cpSpaceHashInit(cpSpaceHashAlloc(), celldim, cells, bbfunc);
186 }
187
188 static void
189 recycleBin(cpSpaceHash *hash, cpSpaceHashBin *bin)
190 {
191 bin.next = hash.pooledBins;
192 hash.pooledBins = bin;
193 }
194
195 static void
196 clearHashCell(cpSpaceHash *hash, int idx)
197 {
198 cpSpaceHashBin *bin = hash.table[idx];
199 while(bin){
200 cpSpaceHashBin *next = bin.next;
201
202 cpHandleRelease(bin.handle, hash.pooledHandles);
203 recycleBin(hash, bin);
204
205 bin = next;
206 }
207
208 hash.table[idx] = null;
209 }
210
211 // Clear all cells in the hashtable.
212 static void
213 clearHash(cpSpaceHash *hash)
214 {
215 for(int i=0; i<hash.numcells; i++)
216 clearHashCell(hash, i);
217 }
218
219 static void freeWrap(void *ptr, void *unused){cpfree(ptr);}
220
221 void
222 cpSpaceHashDestroy(cpSpaceHash *hash)
223 {
224 clearHash(hash);
225
226 cpHashSetFree(hash.handleSet);
227
228 cpArrayEach(hash.allocatedBuffers, &freeWrap, null);
229 cpArrayFree(hash.allocatedBuffers);
230 cpArrayFree(hash.pooledHandles);
231
232 cpfree(hash.table);
233 }
234
235 void
236 cpSpaceHashFree(cpSpaceHash *hash)
237 {
238 if(hash){
239 cpSpaceHashDestroy(hash);
240 cpfree(hash);
241 }
242 }
243
244 void
245 cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells)
246 {
247 // Clear the hash to release the old handle locks.
248 clearHash(hash);
249
250 hash.celldim = celldim;
251 cpSpaceHashAllocTable(hash, next_prime(numcells));
252 }
253
254 // Return true if the chain contains the handle.
255 static cpBool
256 containsHandle(cpSpaceHashBin *bin, cpHandle *hand)
257 {
258 while(bin){
259 if(bin.handle == hand) return cpTrue;
260 bin = bin.next;
261 }
262
263 return cpFalse;
264 }
265
266 // Get a recycled or new bin.
267 static cpSpaceHashBin *
268 getEmptyBin(cpSpaceHash *hash)
269 {
270 cpSpaceHashBin *bin = hash.pooledBins;
271
272 if(bin){
273 hash.pooledBins = bin.next;
274 return bin;
275 } else {
276 // Pool is exhausted, make more
277 int count = CP_BUFFER_BYTES/cpSpaceHashBin.sizeof;
278 assert(count, "Buffer size is too small.");
279
280 cpSpaceHashBin *buffer = cast(cpSpaceHashBin *)cpmalloc(CP_BUFFER_BYTES);
281 cpArrayPush(hash.allocatedBuffers, buffer);
282
283 // push all but the first one, return the first instead
284 for(int i=1; i<count; i++) recycleBin(hash, buffer + i);
285 return buffer;
286 }
287 }
288
289 // The hash function itself.
290 static cpHashValue
291 hash_func(cpHashValue x, cpHashValue y, cpHashValue n)
292 {
293 return cast(cpHashValue)((x*1640531513uL ^ y*2654435789uL) % n);
294 }
295
296 // Much faster than (int)floor(f)
297 // Profiling showed floor() to be a sizable performance hog
298 static int
299 floor_int(cpFloat f)
300 {
301 int i = cast(int)f;
302 return (f < 0.0f && f != i ? i - 1 : i);
303 }
304
305 static void
306 hashHandle(cpSpaceHash *hash, cpHandle *hand, cpBB bb)
307 {
308 // Find the dimensions in cell coordinates.
309 cpFloat dim = hash.celldim;
310 int l = floor_int(bb.l/dim); // Fix by ShiftZ
311 int r = floor_int(bb.r/dim);
312 int b = floor_int(bb.b/dim);
313 int t = floor_int(bb.t/dim);
314
315 int n = hash.numcells;
316 for(int i=l; i<=r; i++){
317 for(int j=b; j<=t; j++){
318 int idx = hash_func(i,j,n);
319 cpSpaceHashBin *bin = hash.table[idx];
320
321 // Don't add an object twice to the same cell.
322 if(containsHandle(bin, hand)) continue;
323
324 cpHandleRetain(hand);
325 // Insert a new bin for the handle in this cell.
326 cpSpaceHashBin *newBin = getEmptyBin(hash);
327 newBin.handle = hand;
328 newBin.next = bin;
329 hash.table[idx] = newBin;
330 }
331 }
332 }
333
334 void
335 cpSpaceHashInsert(cpSpaceHash *hash, void *obj, cpHashValue hashid, cpBB _deprecated_unused)
336 {
337 cpHandle *hand = cast(cpHandle *)cpHashSetInsert(hash.handleSet, hashid, obj, hash);
338 hashHandle(hash, hand, hash.bbfunc(obj));
339 }
340
341 void
342 cpSpaceHashRehashObject(cpSpaceHash *hash, void *obj, cpHashValue hashid)
343 {
344 cpHandle *hand = cast(cpHandle *)cpHashSetRemove(hash.handleSet, hashid, obj);
345
346 if(hand){
347 hand.obj = null;
348 cpHandleRelease(hand, hash.pooledHandles);
349
350 cpSpaceHashInsert(hash, obj, hashid, cpBBNew(0.0f, 0.0f, 0.0f, 0.0f));
351 }
352 }
353
354 static void handleRehashHelper(cpHandle *hand, cpSpaceHash *hash){hashHandle(hash, hand, hash.bbfunc(hand.obj));}
355
356 void
357 cpSpaceHashRehash(cpSpaceHash *hash)
358 {
359 clearHash(hash);
360 cpHashSetEach(hash.handleSet, cast(cpHashSetIterFunc)&handleRehashHelper, hash);
361 }
362
363 void
364 cpSpaceHashRemove(cpSpaceHash *hash, void *obj, cpHashValue hashid)
365 {
366 cpHandle *hand = cast(cpHandle *)cpHashSetRemove(hash.handleSet, hashid, obj);
367
368 if(hand){
369 hand.obj = null;
370 cpHandleRelease(hand, hash.pooledHandles);
371 }
372 }
373
374 struct eachPair {
375 cpSpaceHashIterator func;
376 void *data;
377 }
378
379 static void eachHelper(cpHandle *hand, eachPair *pair){pair.func(hand.obj, pair.data);}
380
381 // Iterate over the objects in the spatial hash.
382 void
383 cpSpaceHashEach(cpSpaceHash *hash, cpSpaceHashIterator func, void *data)
384 {
385 eachPair pair = {func, data};
386 cpHashSetEach(hash.handleSet, cast(cpHashSetIterFunc)&eachHelper, &pair);
387 }
388
389 static void
390 removeOrphanedHandles(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr)
391 {
392 cpSpaceHashBin *bin = *bin_ptr;
393 while(bin){
394 cpHandle *hand = bin.handle;
395 cpSpaceHashBin *next = bin.next;
396
397 if(!hand.obj){
398 // orphaned handle, unlink and recycle the bin
399 (*bin_ptr) = bin.next;
400 recycleBin(hash, bin);
401
402 cpHandleRelease(hand, hash.pooledHandles);
403 } else {
404 bin_ptr = &bin.next;
405 }
406
407 bin = next;
408 }
409 }
410
411 // Calls the callback function for the objects in a given chain.
412 static void
413 query(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr, void *obj, cpSpaceHashQueryFunc func, void *data)
414 {
415 restart:
416 for(cpSpaceHashBin *bin = *bin_ptr; bin; bin = bin.next){
417 cpHandle *hand = bin.handle;
418 void *other = hand.obj;
419
420 if(hand.stamp == hash.stamp || obj == other){
421 continue;
422 } else if(other){
423 func(obj, other, data);
424 hand.stamp = hash.stamp;
425 } else {
426 // The object for this handle has been removed
427 // cleanup this cell and restart the query
428 removeOrphanedHandles(hash, bin_ptr);
429 goto restart; // GCC not smart enough/able to tail call an inlined function.
430 }
431 }
432 }
433
434 void
435 cpSpaceHashPointQuery(cpSpaceHash *hash, cpVect point, cpSpaceHashQueryFunc func, void *data)
436 {
437 cpFloat dim = hash.celldim;
438 int idx = hash_func(floor_int(point.x/dim), floor_int(point.y/dim), hash.numcells); // Fix by ShiftZ
439
440 query(hash, &hash.table[idx], &point, func, data);
441 hash.stamp++;
442 }
443
444 void
445 cpSpaceHashQuery(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data)
446 {
447 // Get the dimensions in cell coordinates.
448 cpFloat dim = hash.celldim;
449 int l = floor_int(bb.l/dim); // Fix by ShiftZ
450 int r = floor_int(bb.r/dim);
451 int b = floor_int(bb.b/dim);
452 int t = floor_int(bb.t/dim);
453
454 int n = hash.numcells;
455 cpSpaceHashBin **table = hash.table;
456
457 // Iterate over the cells and query them.
458 for(int i=l; i<=r; i++){
459 for(int j=b; j<=t; j++){
460 query(hash, &table[hash_func(i,j,n)], obj, func, data);
461 }
462 }
463
464 hash.stamp++;
465 }
466
467 // Similar to struct eachPair above.
468 struct queryRehashPair {
469 cpSpaceHash *hash;
470 cpSpaceHashQueryFunc func;
471 void *data;
472 }
473
474 // Hashset iterator func used with cpSpaceHashQueryRehash().
475 static void
476 handleQueryRehashHelper(void *elt, void *data)
477 {
478 cpHandle *hand = cast(cpHandle *)elt;
479
480 // Unpack the user callback data.
481 queryRehashPair *pair = cast(queryRehashPair *)data;
482 cpSpaceHash *hash = pair.hash;
483 cpSpaceHashQueryFunc func = pair.func;
484
485 cpFloat dim = hash.celldim;
486 int n = hash.numcells;
487
488 void *obj = hand.obj;
489 cpBB bb = hash.bbfunc(obj);
490
491 int l = floor_int(bb.l/dim);
492 int r = floor_int(bb.r/dim);
493 int b = floor_int(bb.b/dim);
494 int t = floor_int(bb.t/dim);
495
496 cpSpaceHashBin **table = hash.table;
497
498 for(int i=l; i<=r; i++){
499 for(int j=b; j<=t; j++){
500 int idx = hash_func(i,j,n);
501 cpSpaceHashBin *bin = table[idx];
502
503 if(containsHandle(bin, hand)) continue;
504
505 cpHandleRetain(hand); // this MUST be done first in case the object is removed in func()
506 query(hash, &bin, obj, func, pair.data);
507
508 cpSpaceHashBin *newBin = getEmptyBin(hash);
509 newBin.handle = hand;
510 newBin.next = bin;
511 table[idx] = newBin;
512 }
513 }
514
515 // Increment the stamp for each object hashed.
516 hash.stamp++;
517 }
518
519 void
520 cpSpaceHashQueryRehash(cpSpaceHash *hash, cpSpaceHashQueryFunc func, void *data)
521 {
522 clearHash(hash);
523
524 queryRehashPair pair = {hash, func, data};
525 cpHashSetEach(hash.handleSet, &handleQueryRehashHelper, &pair);
526 }
527
528 static cpFloat
529 segmentQuery(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr, void *obj, cpSpaceHashSegmentQueryFunc func, void *data)
530 {
531 cpFloat t = 1.0f;
532
533 restart:
534 for(cpSpaceHashBin *bin = *bin_ptr; bin; bin = bin.next){
535 cpHandle *hand = bin.handle;
536 void *other = hand.obj;
537
538 // Skip over certain conditions
539 if(hand.stamp == hash.stamp){
540 continue;
541 } else if(other){
542 t = cpfmin(t, func(obj, other, data));
543 hand.stamp = hash.stamp;
544 } else {
545 // The object for this handle has been removed
546 // cleanup this cell and restart the query
547 removeOrphanedHandles(hash, bin_ptr);
548 goto restart; // GCC not smart enough/able to tail call an inlined function.
549 }
550 }
551
552 return t;
553 }
554
555 // modified from http://playtechs.blogspot.com/2007/03/raytracing-on-grid.html
556 void cpSpaceHashSegmentQuery(cpSpaceHash *hash, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpaceHashSegmentQueryFunc func, void *data)
557 {
558 a = cpvmult(a, 1.0f/hash.celldim);
559 b = cpvmult(b, 1.0f/hash.celldim);
560
561 int cell_x = floor_int(a.x), cell_y = floor_int(a.y);
562
563 cpFloat t = 0;
564
565 int x_inc, y_inc;
566 cpFloat temp_v, temp_h;
567
568 if (b.x > a.x){
569 x_inc = 1;
570 temp_h = (cpffloor(a.x + 1.0f) - a.x);
571 } else {
572 x_inc = -1;
573 temp_h = (a.x - cpffloor(a.x));
574 }
575
576 if (b.y > a.y){
577 y_inc = 1;
578 temp_v = (cpffloor(a.y + 1.0f) - a.y);
579 } else {
580 y_inc = -1;
581 temp_v = (a.y - cpffloor(a.y));
582 }
583
584 // Division by zero is *very* slow on ARM
585 cpFloat dx = cpfabs(b.x - a.x), dy = cpfabs(b.y - a.y);
586 cpFloat dt_dx = (dx ? 1.0f/dx : INFINITY), dt_dy = (dy ? 1.0f/dy : INFINITY);
587
588 // fix NANs in horizontal directions
589 cpFloat next_h = (temp_h ? temp_h*dt_dx : dt_dx);
590 cpFloat next_v = (temp_v ? temp_v*dt_dy : dt_dy);
591
592 cpSpaceHashBin **table = hash.table;
593
594 int n = hash.numcells;
595 while(t < t_exit){
596 int idx = hash_func(cell_x, cell_y, n);
597 t_exit = cpfmin(t_exit, segmentQuery(hash, &table[idx], obj, func, data));
598
599 if (next_v < next_h){
600 cell_y += y_inc;
601 t = next_v;
602 next_v += dt_dy;
603 } else {
604 cell_x += x_inc;
605 t = next_h;
606 next_h += dt_dx;
607 }
608 }
609
610 hash.stamp++;
611 }