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 }