1 /**************************************************************************
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 3
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 **************************************************************************/
12 // code by Ketmar Dark
14 // note that entity can occupy more than one cell
15 class EntityGrid : Object;
17 //#define ENTITY_GRID_HAS_SCHEDULED_UPDATES
24 int tag; // arbitrary userdata, set to 0 in new items
25 // last known grid position
26 int gx0, gy0, gx1, gy1;
27 // previous item in alloced list
29 // next item in free/alloced list
31 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
38 int cid; // index in `cobjs`
40 // otherwise either next item in cell, or next item in free list
45 private array!CellObject cobjs;
46 private int freeHeadCObj;
47 private int firstInsertedCObj; // for `allObjects()`
48 private int lastInsertedCObj; // for `allObjects()`
50 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
51 private array!int updateSchedule;
54 // item #0 is never used
55 private array!Item items;
58 //WARNING! don't change
60 private int mWidth, mHeight;
61 // offset of the (0, 0) grid cell, in pixels
62 private int mXOfs, mYOfs;
64 // grid cells, width*height in total
65 // contains index in `items` or 0
66 private array!int grid;
68 class!MapEntity objectType = MapEntity;
75 final int gridX0 { get mXOfs; }
76 final int gridY0 { get mYOfs; }
77 final int gridX1 { get { return mXOfs+mWidth*CellSize-1; } }
78 final int gridY1 { get { return mYOfs+mHeight*CellSize-1; } }
79 final int gridWidth { get { return mWidth*CellSize; } }
80 final int gridHeight { get { return mHeight*CellSize; } }
83 // ////////////////////////////////////////////////////////////////////////// //
84 final void setup (int x0, int y0, int awidth, int aheight, optional class!MapEntity aotype) {
85 if (specified_aotype) {
86 if (!aotype) aotype = MapEntity;
89 awidth = max(1, awidth);
90 aheight = max(1, aheight);
91 int xcells = max(1, (awidth+CellSize-1)/CellSize);
92 int ycells = max(1, (aheight+CellSize-1)/CellSize);
99 // setup items and free list
102 grid.setSize(xcells, ycells);
107 final void clear () {
121 final void removeAllObjects (optional bool doRemove) {
124 foreach (ref auto oid; grid) oid = 0;
127 foreach (int f, ref auto item; items) item.next = f+1;
128 items[0].next = 0; // as sentinel
132 for (int f = firstInsertedCObj; f; f = cobjs[f].next) {
133 if (doRemove || cobjs[f].objectIsOwned) delete cobjs[f].e; else cobjs[f].e = none;
136 foreach (int f, ref auto cobj; cobjs) {
141 cobj.objectIsOwned = false;
143 cobjs[0].next = 0; // as sentinel
146 firstInsertedCObj = 0;
147 lastInsertedCObj = 0;
151 final void clearTags () {
152 for (int oid = firstInsertedCObj; oid; oid = cobjs[oid].next) {
158 final int nextTag () {
159 if (lastUsedTag == 0x7fff_ffff) {
163 return ++lastUsedTag;
167 // ////////////////////////////////////////////////////////////////////////// //
168 private final int allocCObj () {
169 int res = freeHeadCObj;
171 freeHeadCObj = cobjs[res].next;
174 int newlen = (res > 3 ? res+res/2 : 8);
175 cobjs.length = newlen;
176 foreach (int f; res+1..newlen) { cobjs[f].prev = -1; cobjs[f].next = f+1; }
178 freeHeadCObj = res+1;
180 // other fields will be set by caller
181 auto cobj = &cobjs[res];
182 if (lastInsertedCObj) cobjs[lastInsertedCObj].next = res; else firstInsertedCObj = res;
184 cobj.prev = lastInsertedCObj;
186 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
187 cobj.updateScheduled = false;
189 cobj.objectIsOwned = ownObjects;
190 lastInsertedCObj = res;
195 private final void freeCObj (int idx) {
196 if (idx < 1 || idx >= cobjs.length) FatalError("EntityGrid::freeCObj: invalid item index!");
197 auto cobj = &cobjs[idx];
198 if (cobj.prev == -1) FatalError("EntityGrid::freeCObj: double free!");
199 // remove us from the alloced list
200 if (cobj.prev) cobjs[cobj.prev].next = cobj.next;
201 if (cobj.next) cobjs[cobj.next].prev = cobj.prev;
202 // fix alloced list (and do some sanity checks)
203 if (idx == lastInsertedCObj) {
204 if (cobj.next != 0) FatalError("EntityGrid::freeCObj: invalid alloced list!");
205 lastInsertedCObj = cobj.prev;
207 if (idx == firstInsertedCObj) {
208 if (cobj.prev != 0) FatalError("EntityGrid::freeCObj: invalid alloced list!");
209 firstInsertedCObj = cobj.next;
211 MapEntity e = cobj.e;
212 if (cobj.objectIsOwned) {
220 cobj.next = freeHeadCObj;
221 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
222 cobj.updateScheduled = false;
224 cobj.objectIsOwned = false;
229 private final int allocItem () {
232 freeHead = items[res].next;
235 int newlen = (res > 3 ? res+res/2 : 8);
236 items.length = newlen;
237 foreach (int f; res+1..newlen) items[f].next = f+1;
241 // other fields will be set by caller
246 private final void freeItem (int idx) {
247 if (idx < 1 || idx >= items.length) FatalError("EntityGrid::freeItem: invalid item index!");
248 if (items[idx].cid == -1) FatalError("EntityGrid::freeItem: double free!");
250 items[idx].next = freeHead;
255 // ////////////////////////////////////////////////////////////////////////// //
256 private final void insertIntoCell (int cid, int gx, int gy) {
257 int iid = allocItem();
258 Item *it = &items[iid];
260 it.next = grid[gx, gy];
265 private final void removeFromCell (int cid, int gx, int gy) {
267 int iid = grid[gx, gy], iprev = 0;
269 if (items[iid].cid == cid) break;
271 iid = items[iid].next;
275 int next = items[iid].next;
276 if (iprev) items[iprev].next = next; else grid[gx, gy] = next;
282 // `cobjs[cid]` should be fully initialized
283 private final void insertInternal (int cid) {
284 auto cobj = &cobjs[cid];
285 int gx0 = cobj.gx0, gx1 = cobj.gx1;
286 foreach (int cy; cobj.gy0..cobj.gy1) {
287 foreach (int cx; gx0..gx1) {
288 insertIntoCell(cid, cx, cy);
294 // this won't free `cid`
295 private final void removeInternal (int cid) {
296 auto cobj = &cobjs[cid];
297 int gx0 = cobj.gx0, gx1 = cobj.gx1;
298 foreach (int cy; cobj.gy0..cobj.gy1) {
299 foreach (int cx; gx0..gx1) {
300 removeFromCell(cid, cx, cy);
306 // returns `true` if moved
307 private final bool moveInternal (int cid, int nx0, int ny0, int nx1, int ny1) {
308 auto cobj = &cobjs[cid];
309 int gx0 = cobj.gx0, gy0 = cobj.gy0;
310 int gx1 = cobj.gx1, gy1 = cobj.gy1;
311 if (gx0 != nx0 || gy0 != ny0 || gx1 != nx1 || gy1 != ny1) {
312 foreach (int gy; min(gy0, ny0)..max(gy1, ny1)) {
313 foreach (int gx; min(gx0, nx0)..max(gx1, nx1)) {
314 // are we inside the old rect?
315 if (gx >= gx0 && gy >= gy0 && gx < gx1 && gy < gy1) {
317 // if not inside new, remove
318 if (gx < nx0 || gy < ny0 || gx >= nx1 || gy >= ny1) {
319 removeFromCell(cid, gx, gy);
323 // if inside new, insert
324 if (gx >= nx0 && gy >= ny0 && gx < nx1 && gy < ny1) {
325 insertIntoCell(cid, gx, gy);
330 //removeInternal(cid);
331 cobj.gx0 = nx0; cobj.gy0 = ny0;
332 cobj.gx1 = nx1; cobj.gy1 = ny1;
333 //insertInternal(cid);
340 // `false`: out of grid/too thin/etc
341 // returned coords are always valid
342 // also, `gx1` and `gy1` are exclusive
343 final bool calcObjGridPos (MapEntity e, out int gx0, out int gy0, out int gx1, out int gy1) {
344 if (!e) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // no object
345 int ew = max(1, e.width), eh = max(1, e.height);
346 //if (ew < 1 || eh < 1) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; }
347 int ex0 = e.x0-mXOfs, ey0 = e.y0-mYOfs;
348 int ex1 = ex0+ew-1, ey1 = ey0+eh-1;
349 // check if it fits in grid at all
350 if (ex1 < 0 || ey1 < 0) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // out of grid
351 int tgw = mWidth, tgh = mHeight;
352 if (ex0 >= tgw*CellSize || ey0 >= tgh*CellSize) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // out of grid
358 gx0 = clamp(ex0, 0, tgw-1);
359 gy0 = clamp(ey0, 0, tgh-1);
360 gx1 = clamp(ex1+1, 0, tgw);
361 gy1 = clamp(ey1+1, 0, tgh);
362 return (gx1 > gx0 && gy1 > gy0);
366 // ////////////////////////////////////////////////////////////////////////// //
367 // returns 0 if item is not put in grid, or cid
368 final int insert (MapEntity e) {
371 if (e.grid != self) FatalError("cannot instert entity in two grids");
372 if (!update(e.gridId)) { e.gridId = 0; e.grid = none; }
375 int gx0, gy0, gx1, gy1;
376 bool doInsert = calcObjGridPos(e, out gx0, out gy0, out gx1, out gy1);
378 if (e isa MapTileRope) {
379 writeln("ROPE; pos=(", e.x0, ",", e.y0, "); size=(", e.width, "x", e.height, "); grid=(", gx0, ",", gy0, ")-(", gx1, ",", gy1, ")");
383 if (e isa TitleTileCopy) {
384 writeln("ROPE; pos=(", e.x0, ",", e.y0, "); size=(", e.width, "x", e.height, "); grid=(", gx0, ",", gy0, ")-(", gx1, ",", gy1, ")");
388 int cid = allocCObj();
389 auto cobj = &cobjs[cid];
394 cobj.gx0 = gx0; cobj.gy0 = gy0;
395 cobj.gx1 = gx1; cobj.gy1 = gy1;
396 if (doInsert) insertInternal(cid);
401 // returns `true` if item was in the grid, and now removed
402 final bool remove (int cid) {
403 if (cid < 1 || cid >= cobjs.length) return false;
404 if (cobjs[cid].prev == -1) return false;
407 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
408 foreach (int idx, int v; updateSchedule) {
410 updateSchedule.remove(idx);
419 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
420 // returns `true` if item will be updated
421 final bool scheduleUpdate (int cid) {
422 if (cid < 1 || cid >= cobjs.length) return false;
423 auto cobj = &cobjs[cid];
424 if (cobj.prev == -1) return false;
425 if (cobj.updateScheduled) return true; // already marked
426 MapEntity e = cobj.e;
429 cobjs[cid].updateScheduled = true;
430 updateSchedule[$] = cid;
433 int gx0, gy0, gx1, gy1;
434 calcObjGridPos(cobj.e, out gx0, out gy0, out gx1, out gy1);
435 if (gx0 != cobj.gx0 || gy0 != cobj.gy0 || gx1 != cobj.gx1 || gy1 == cobj.gy1) {
437 cobjs[cid].updateScheduled = true;
438 updateSchedule[$] = cid;
444 final void performUpdates () {
445 while (updateSchedule.length) {
446 int cid = updateSchedule[$-1];
447 updateSchedule.length -= 1;
454 // call this when object changed its position
455 // it is harmless to call it even if position wasn't changed
456 // returns `false` if item should be removed from grid
457 // optionally sets `wasMoved` flag
458 final bool update (int cid, optional out bool wasMoved/*, optional bool markAsDead*/) {
459 if (specified_wasMoved) wasMoved = false;
460 if (cid < 1 || cid >= cobjs.length) return false;
461 auto cobj = &cobjs[cid];
462 if (cobj.prev == -1) return false;
463 MapEntity e = cobj.e;
464 if (!e) return false;
466 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
467 cobjs[cid].updateScheduled = false;
470 int gx0, gy0, gx1, gy1;
471 if (!calcObjGridPos(e, out gx0, out gy0, out gx1, out gy1)) {
472 //if (markAsDead && !e.isZeroDimsValid && (e.width < 1 || e.height < 1)) e.instanceRemove();
474 cobj.gx0 = 0; cobj.gy0 = 0;
475 cobj.gx1 = 0; cobj.gy1 = 0;
478 wasMoved = moveInternal(cid, gx0, gy0, gx1, gy1);
483 // ////////////////////////////////////////////////////////////////////////// //
484 // snap coords to grid cell
485 final void snapXY (ref int x, ref int y) {
486 x = (x-mXOfs)/CellSize*CellSize+mXOfs;
487 y = (y-mYOfs)/CellSize*CellSize+mYOfs;
492 final void calcCellSize (out int xcount, out int ycount, int x, int y, int w, int h) {
493 if (w < 1 || h < 1) {
501 xcount = (x1-x)/CellSize+1;
502 ycount = (y1-y)/CellSize+1;
508 // ////////////////////////////////////////////////////////////////////////// //
509 // ok, it is not a real spawner, i just wanted to fake its return type
510 final spawner MapEntity getObject (class!MapEntity cc, int cid) {
511 if (cid < 1 || cid >= cobjs.length) return none;
512 auto cobj = &cobjs[cid];
513 if (cobj.prev == -1) return none;
517 final bool getOwned (int cid) {
518 if (cid < 1 || cid >= cobjs.length) return false;
519 auto cobj = &cobjs[cid];
520 return cobj.objectIsOwned;
523 final void setOwned (int cid, bool v) {
524 if (cid < 1 || cid >= cobjs.length) return;
525 auto cobj = &cobjs[cid];
526 if (cobj.prev != -1) cobj.objectIsOwned = v;
530 // ////////////////////////////////////////////////////////////////////////// //
531 // ok, it is not a real spawner, i just wanted to fake its return type
532 final spawner MapEntity getObjectByItem (class!MapEntity cc, int id) {
533 if (id < 1 || id >= items.length) return none;
534 MapEntity e = cobjs[items[id].cid].e;
535 if (e && (!e.visible || !e.isInstanceAlive)) return none;
540 // returns first item in cell, optionally tags it too (and skips the given tag)
541 // returns 0 if there are no items
542 final int getFirstItemInCell (int cx, int cy, optional int tag) {
543 //writeln("specified_tag=", specified_tag, "; tag=", tag);
544 if (cx < 0 || cy < 0 || cx >= mWidth || cy >= mHeight) return 0;
545 int oid = grid[cx, cy];
549 CellObject *co = &cobjs[items[oid].cid];
551 //writeln("***TAG SKIP (0)");
552 oid = items[oid].next;
555 co.tag = tag; // mark it
563 // returns first item in cell, optionally tags it too (and skips the given tag)
564 // returns 0 if there are no items
565 final int getFirstItem (int x, int y, optional int tag) {
566 return getFirstItemInCell((x-mXOfs)/CellSize, (y-mYOfs)/CellSize, tag!optional);
570 // returns next item in list, optionally tags it too (and skips the given tag)
571 // returns 0 if there are no more items
572 final int getNextItem (int oid, optional int tag) {
573 oid = items[oid].next; // 0 contains sentinel anyway
577 CellObject *co = &cobjs[items[oid].cid];
579 //writeln("***TAG SKIP (1)");
580 oid = items[oid].next;
583 co.tag = tag; // mark it
591 // ////////////////////////////////////////////////////////////////////////// //
600 final bool inCellPix_opIterInit (ref CellIter it, int x, int y, optional int tag, bool precise) {
601 //if (!specified_precise) precise = true;
602 int oid = getFirstItem(x, y, tag!optional);
604 MapEntity e = getObjectByItem(objectType, oid);
605 if (e && (!precise || e.isPointCollision(x, y))) {
610 it.precise = precise;
611 it.hasTag = specified_tag;
614 oid = getNextItem(oid, tag!optional);
619 final bool inCellPix_opIterNext (ref CellIter it, out MapEntity ge) {
620 int oid = it.oid, x = it.px, y = it.py;
621 bool hasTag = it.hasTag;
622 bool precise = it.precise;
625 MapEntity e = getObjectByItem(objectType, oid);
626 oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
627 if (e && (!precise || e.isPointCollision(x, y))) {
637 // ////////////////////////////////////////////////////////////////////////// //
638 struct CellRectIter {
649 final bool inRectPix_opIterInit (ref CellRectIter it, int ax, int ay, int aw, int ah, optional int tag, bool precise) {
650 if (aw < 1 || ah < 1) return false;
651 //if (!specified_precise) precise = true;
652 it.x0 = ax; it.y0 = ay;
653 it.w = aw; it.h = ah;
654 int grX = ax-mXOfs, grY = ay-mYOfs;
655 if (grX+aw <= 0 || grY+ah <= 0 || grX >= mWidth*CellSize || grY >= mHeight*CellSize) return false;
656 int cx0 = max(0, grX/CellSize);
657 int cy0 = max(0, grY/CellSize);
658 int cx1 = min(mWidth, (grX+aw-1)/CellSize+1);
659 int cy1 = min(mHeight, (grY+ah-1)/CellSize+1);
660 //writeln("(", ax, ",", ay, ")-(", ax+aw-1, ",", ay+ah-1, "): (", cx0, ",", cy0, ")-(", cx1, ",", cy1, ")");
661 foreach (int cy; cy0..cy1) {
662 foreach (int cx; cx0..cx1) {
663 int oid = getFirstItemInCell(cx, cy, tag!optional);
665 MapEntity e = getObjectByItem(objectType, oid);
666 if (e && (!precise || e.isRectCollision(ax, ay, aw, ah))) {
667 //if (e) writeln("!!! cx=", cx, "; cy=", cy, " (", GetClassName(e.Class), ") (name:", e.objName, "; type:", e.objType, ")");
669 it.cx = cx; it.cy = cy;
670 it.cx0 = cx0; it.cy0 = cy0;
671 it.cx1 = cx1; it.cy1 = cy1;
673 it.hasTag = specified_tag;
674 it.precise = precise;
677 oid = getNextItem(oid, tag!optional);
684 final bool inRectPix_opIterNext (ref CellRectIter it, out MapEntity ge) {
686 bool hasTag = it.hasTag;
688 int ix = it.x0, iy = it.y0, iw = it.w, ih = it.h;
689 bool precise = it.precise;
691 MapEntity e = getObjectByItem(objectType, oid);
692 oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
693 if (e && (!precise || e.isRectCollision(ix, iy, iw, ih))) {
699 // move to the next cell
701 foreach (int cy; it.cy..it.cy1) {
702 foreach (int cx; it.cx..it.cx1) {
703 oid = (hasTag ? getFirstItemInCell(cx, cy, tag) : getFirstItemInCell(cx, cy));
705 MapEntity e = getObjectByItem(objectType, oid);
706 oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
707 if (e && (!precise || e.isRectCollision(ix, iy, iw, ih))) {
723 // ////////////////////////////////////////////////////////////////////////// //
725 final int getFirstObject () { return firstInsertedCObj; }
727 // 0: no more objects
728 final int getNextObject (int cid, optional bool removeThis) {
729 if (cid < 1 || cid >= cobjs.length || cobjs[cid].prev == -1) return 0;
730 int n = cobjs[cid].next;
740 final int getLastObject () { return lastInsertedCObj; }
742 // 0: no more objects
743 final int getPrevObject (int cid, optional bool removeThis) {
744 if (cid < 1 || cid >= cobjs.length || cobjs[cid].prev == -1) return 0;
745 int n = cobjs[cid].prev;
754 // ////////////////////////////////////////////////////////////////////////// //
755 final bool allObjects_opIterInit (ref int cid) {
756 cid = firstInsertedCObj;
757 while (cid && !cobjs[cid].e) cid = cobjs[cid].next;
761 final bool allObjects_opIterNext (ref int cid, out MapEntity ge) {
762 if (!cid) return false;
765 cid = cobjs[cid].next;
772 final bool allObjectsBackwards_opIterInit (ref int cid) {
773 cid = lastInsertedCObj;
774 while (cid && !cobjs[cid].e) cid = cobjs[cid].prev;
778 final bool allObjectsBackwards_opIterNext (ref int cid, out MapEntity ge) {
781 cid = cobjs[cid].prev;
788 // ////////////////////////////////////////////////////////////////////////// //
790 private static final void siftDownCIDSort (ref array!int arr, int start, int end, bool delegate (int cida, int cidb) dgLess) {
793 auto child = 2*root+1; // left child
794 if (child > end) break;
796 if (dgLess(arr[swap], arr[child])) swap = child;
797 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
798 if (swap == root) break;
799 auto tmp = arr[swap];
800 arr[swap] = arr[root];
807 static final void sortCIDList (ref array!int arr, bool delegate (int a, int b) dgLess, optional int count) {
808 if (!specified_count) count = arr.length;
809 if (count < 2 || !dgLess) return; // nothing to do
812 if (dgLess(arr[1], arr[0])) {
823 auto start = (end-1)/2; // parent; always safe, as our array has at least two items
825 //siftDownCIDSort(arr, start, end, dgLess);
828 auto child = 2*root+1; // left child
829 if (child > end) break;
831 if (dgLess(arr[swap], arr[child])) swap = child;
832 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
833 if (swap == root) break;
834 auto tmp = arr[swap];
835 arr[swap] = arr[root];
839 if (start-- == 0) break; // as `start` cannot be negative, use this condition
847 //siftDownCIDSort(arr, 0, end, dgLess);
850 auto child = 2*root+1; // left child
851 if (child > end) break;
853 if (dgLess(arr[swap], arr[child])) swap = child;
854 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
855 if (swap == root) break;
857 arr[swap] = arr[root];
866 private static final void siftDownObjSort (ref array!MapEntity arr, int start, int end, bool delegate (MapEntity a, MapEntity b) dgLess) {
869 auto child = 2*root+1; // left child
870 if (child > end) break;
872 if (dgLess(arr[swap], arr[child])) swap = child;
873 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
874 if (swap == root) break;
875 auto tmp = arr[swap];
876 arr[swap] = arr[root];
883 static final void sortEntList (ref array!MapEntity arr, bool delegate (MapEntity a, MapEntity b) dgLess, optional int count) {
884 if (!specified_count) count = arr.length;
885 if (count < 2 || !dgLess) return; // nothing to do
888 if (dgLess(arr[1], arr[0])) {
899 auto start = (end-1)/2; // parent; always safe, as our array has at least two items
901 //siftDownObjSort(arr, start, end, dgLess);
904 auto child = 2*root+1; // left child
905 if (child > end) break;
907 if (dgLess(arr[swap], arr[child])) swap = child;
908 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
909 if (swap == root) break;
910 auto tmp = arr[swap];
911 arr[swap] = arr[root];
915 if (start-- == 0) break; // as `start` cannot be negative, use this condition
923 //siftDownObjSort(arr, 0, end, dgLess);
926 auto child = 2*root+1; // left child
927 if (child > end) break;
929 if (dgLess(arr[swap], arr[child])) swap = child;
930 if (child+1 <= end && dgLess(arr[swap], arr[child+1])) swap = child+1;
931 if (swap == root) break;
933 arr[swap] = arr[root];
941 // ////////////////////////////////////////////////////////////////////////// //
942 private final bool cmpSortXYRev (int cida, int cidb) {
943 MapEntity a = cobjs[cida].e;
944 MapEntity b = cobjs[cidb].e;
945 if (!a) return !!b; // empty a is always less, except if b is empty too
946 if (!b) return false; // non empty a is never less than empty b
947 if (a.y0 == b.y0) return (a.x0 > b.x0);
948 return (a.y0 > b.y0);
952 // ////////////////////////////////////////////////////////////////////////// //
953 struct CellItemIter {
954 int gx, gy; // next cell
956 // in current cell, sorted backwards, so we can use [$-1] and decrement length
961 // from the right-bottom to left-up
962 final void getAllObjectsBackSorted (ref array!int cidlist, optional bool includeAll) {
964 for (int f = lastInsertedCObj; f; f = cobjs[f].prev) {
965 MapEntity e = cobjs[f].e;
966 if (e && (includeAll || (e.visible && e.isInstanceAlive))) cidlist[$] = f;
968 sortCIDList(cidlist, &cmpSortXYRev);
973 final bool allCellsBackwards_opIterInit (ref CellItemIter it) {
974 it.cidlist.length -= it.cidlist.length; // just in case
976 foreach (int gy; 0..mHeight; backward) {
977 foreach (int gx; 0..mWidth; backward) {
978 int oid = getFirstInCell(gx, gy, tag);
980 MapEntity e = getObject(MapEntity, oid);
981 if (e) it.cidlist[$] = items[oid].cid;
982 oid = getNext(oid, tag);
984 if (it.cidlist.length) {
985 sortCIDList(it.cidlist, &cmpSortXYRev);
996 final bool allCellsBackwards_opIterNext (ref CellItemIter it, out MapEntity ge) {
997 while (it.cidlist.length) {
998 int cid = it.cidlist[$-1];
999 it.cidlist.length -= 1;
1001 if (ge) return true;
1006 foreach (int gy; 0..it.gy+1; backward) {
1007 foreach (int gx; 0..it.gx+1; backward) {
1008 int oid = getFirstInCell(gx, gy, tag);
1010 MapEntity e = getObject(MapEntity, oid);
1011 if (e) it.cidlist[$] = items[oid].cid;
1012 oid = getNext(oid, tag);
1014 if (it.cidlist.length) {
1015 sortCIDList(it.cidlist, &cmpSortXYRev);
1018 int cid = it.cidlist[$-1];
1019 it.cidlist.length -= 1;
1031 // ////////////////////////////////////////////////////////////////////////// //
1033 final int countObjects () {
1035 for (int f = firstInsertedCObj; f; f = cobjs[f].next) ++objCount;
1040 final void dumpStats () {
1041 // calculate max object in cell, number of free cells, and number of used items
1042 int freeCells = 0, maxInCell = 0, itemCount = 0;
1043 foreach (int oid; grid) {
1050 oid = items[oid].next;
1053 maxInCell = max(maxInCell, xc);
1056 int objCount = countObjects();
1057 writeln("=========== GRID STATS ===========");
1058 writeln("bounds: (", mXOfs, ",", mYOfs, ")-(", mXOfs+mWidth*CellSize-1, ",", mYOfs+mHeight*CellSize-1, ")");
1059 writeln("cells: ", grid.length, " (", grid.length1, "x", grid.length2, ")");
1060 writeln("objects: ", objCount, " (out of ", cobjs.length, " slots)");
1061 writeln("used items: ", itemCount, " out of ", items.length);
1062 writeln("max items in cell: ", maxInCell);
1063 writeln("used cells: ", grid.length-freeCells, " out of ", grid.length);
1064 writeln("----------------------------------");
1068 // ////////////////////////////////////////////////////////////////////////// //
1070 int wx0, wy0, wx1, wy1; // window coordinates
1071 int stx, sty; // "steps" for x and y axes
1072 int stleft; // "steps left"
1073 int err, errinc, errmax;
1074 int xd, yd; // current coord
1082 // call `lwSetup()` after this
1083 static final void lwCreate (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
1092 static final void lwSetClip (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
1101 // this will use `w[xy][01]` to clip coords
1102 // return `false` if the whole line was clipped away
1103 // on `true`, you should process first point, and go on
1104 static final bool lwSetup (ref LineWalker lw, int x0, int y0, int x1, int y1) {
1105 if (lw.wx1 < lw.wx0 || lw.wy1 < lw.wy0) { lw.stleft = 0; lw.xd = x0; lw.yd = y0; return false; }
1108 if (x0 >= lw.wx0 && y0 >= lw.wy0 && x0 <= lw.wx1 && y0 <= lw.wy1 &&
1109 x1 >= lw.wx0 && y1 >= lw.wy0 && x1 <= lw.wx1 && y1 <= lw.wy1)
1113 float sx0 = x0, sy0 = y0;
1114 float sx1 = x1, sy1 = y1;
1115 res = Geom.clipLine(sx0, sy0, sx1, sy1, lw.wx0, lw.wy0, lw.wx1, lw.wy1);
1116 if (!res) { lw.stleft = 0; lw.xd = x0; lw.yd = y0; return false; }
1117 x0 = trunc(sx0); y0 = trunc(sy0);
1118 x1 = trunc(sx1); y1 = trunc(sy1);
1121 // check for ortho lines
1125 lw.stleft = abs(x1-x0)+1;
1126 lw.stx = (x0 < x1 ? 1 : -1);
1129 lw.errmax = 10; // anything that is greater than zero
1130 } else if (x0 == x1) {
1133 lw.stleft = abs(y1-y0)+1;
1135 lw.sty = (y0 < y1 ? 1 : -1);
1137 lw.errmax = 10; // anything that is greater than zero
1140 if (abs(x1-x0) >= abs(y1-y0)) {
1143 lw.stleft = abs(x1-x0)+1;
1144 lw.errinc = abs(y1-y0)+1;
1148 lw.stleft = abs(y1-y0)+1;
1149 lw.errinc = abs(x1-x0)+1;
1151 lw.stx = (x0 < x1 ? 1 : -1);
1152 lw.sty = (y0 < y1 ? 1 : -1);
1153 lw.errmax = lw.stleft;
1157 lw.err = -lw.errmax;
1162 // call this *after* doing a step
1163 // WARNING! if you will do a step when this returns `true`, you will fall into limbo
1164 static final bool lwIsDone (ref LineWalker lw) {
1165 return (lw.stleft <= 0);
1169 // as you will prolly call `done()` after doing a step anyway, this will do it for you
1170 // move to next point, return `true` when the line is complete (i.e. you should stop)
1171 static final bool step (ref LineWalker lw) {
1174 lw.err += lw.errinc;
1175 if (lw.err >= 0) { lw.err -= lw.errmax; lw.yd += lw.sty; }
1178 lw.err += lw.errinc;
1179 if (lw.err >= 0) { lw.err -= lw.errmax; lw.xd += lw.stx; }
1182 return (lw.stleft <= 0);
1186 // move to next tile; return `true` if the line is complete (and walker state is undefined then)
1187 static final bool stepToNextTile (ref LineWalker lw) {
1188 if (lw.stleft < 2) return true; // max one pixel left, nothing to do
1192 // strictly horizontal?
1197 ex = (lw.xd&(~(CellSize-1)))-1;
1198 lw.stleft -= lw.xd-ex;
1200 // xd: to right edge
1201 ex = (lw.xd|(CellSize-1))+1;
1202 lw.stleft -= ex-lw.xd;
1205 return (lw.stleft <= 0);
1208 // strictly vertical?
1213 ey = (lw.yd&(~(CellSize-1)))-1;
1214 lw.stleft -= lw.yd-ey;
1216 // yd: to bottom edge
1217 ey = (lw.yd|(CellSize-1))+1;
1218 lw.stleft -= ey-lw.yd;
1221 return (lw.stleft <= 0);
1229 ex = (lw.xd&(~(CellSize-1)))-1;
1232 ex = (lw.xd|(CellSize-1))+1;
1238 ey = (lw.yd&(~(CellSize-1)))-1;
1241 ey = (lw.yd|(CellSize-1))+1;
1245 int wklen = (xwalk <= ywalk ? xwalk : ywalk);
1247 // in which dir we want to walk?
1249 if (lw.stleft <= 0) return true;
1251 lw.xd += wklen*lw.stx;
1252 for (int f = wklen; f > 0; --f) {
1253 lw.err += lw.errinc;
1254 if (lw.err >= 0) { lw.err -= lw.errmax; lw.yd += lw.sty; }
1257 lw.yd += wklen*lw.sty;
1258 for (int f = wklen; f > 0; --f) {
1259 lw.err += lw.errinc;
1260 if (lw.err >= 0) { lw.err -= lw.errmax; lw.xd += lw.stx; }
1263 // check for walk completion
1264 if (lw.xd == ex || lw.yd == ey) break;