alot of depth fixes
[k8vacspelynky.git] / mapent / EntityGrid.vc
blob5348864f2ca0ab84ed8c63a41e882019342ac2d2
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.
6  *
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
13 // grid of entities
14 // note that entity can occupy more than one cell
15 class EntityGrid : Object;
17 //#define ENTITY_GRID_HAS_SCHEDULED_UPDATES
19 enum CellSize = 16;
22 struct CellObject {
23   MapEntity e;
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
28   int prev;
29   // next item in free/alloced list
30   int next;
31 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
32   bool updateScheduled;
33 #endif
34   bool objectIsOwned;
37 struct Item {
38   int cid; // index in `cobjs`
39   // 0: end-of-list
40   // otherwise either next item in cell, or next item in free list
41   int next;
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;
52 #endif
54 // item #0 is never used
55 private array!Item items;
56 private int freeHead;
58 //WARNING! don't change
59 // grid size in cells
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;
70 int lastUsedTag;
72 bool ownObjects;
74 // in pixels
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;
87     objectType = aotype;
88   }
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);
93   // setup offsets
94   mXOfs = x0;
95   mYOfs = y0;
96   // setup dimensions
97   mWidth = xcells;
98   mHeight = ycells;
99   // setup items and free list
100   items.length = 32;
101   cobjs.length = 32;
102   grid.setSize(xcells, ycells);
103   removeAllObjects();
107 final void clear () {
108   cobjs.length = 0;
109   freeHeadCObj = 0;
110   items.length = 0;
111   freeHead = 0;
112   mWidth = 0;
113   mHeight = 0;
114   mXOfs = 0;
115   mYOfs = 0;
116   grid.length = 0;
117   lastUsedTag = 0;
121 final void removeAllObjects (optional bool doRemove) {
122   lastUsedTag = 0;
123   // clear grid
124   foreach (ref auto oid; grid) oid = 0;
125   // clear items
126   items.length = 32;
127   foreach (int f, ref auto item; items) item.next = f+1;
128   items[0].next = 0; // as sentinel
129   items[$-1].next = 0;
130   freeHead = 1;
131   // clear cobjects
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;
134   }
135   cobjs.length = 32;
136   foreach (int f, ref auto cobj; cobjs) {
137     cobj.e = none;
138     cobj.prev = -1;
139     cobj.next = f+1;
140     cobj.tag = 0;
141     cobj.objectIsOwned = false;
142   }
143   cobjs[0].next = 0; // as sentinel
144   cobjs[$-1].next = 0;
145   freeHeadCObj = 1;
146   firstInsertedCObj = 0;
147   lastInsertedCObj = 0;
151 final void clearTags () {
152   for (int oid = firstInsertedCObj; oid; oid = cobjs[oid].next) {
153     cobjs[oid].tag = 0;
154   }
158 final int nextTag () {
159   if (lastUsedTag == 0x7fff_ffff) {
160     lastUsedTag = 0;
161     clearTags();
162   }
163   return ++lastUsedTag;
167 // ////////////////////////////////////////////////////////////////////////// //
168 private final int allocCObj () {
169   int res = freeHeadCObj;
170   if (res) {
171     freeHeadCObj = cobjs[res].next;
172   } else {
173     res = cobjs.length;
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; }
177     cobjs[$-1].next = 0;
178     freeHeadCObj = res+1;
179   }
180   // other fields will be set by caller
181   auto cobj = &cobjs[res];
182   if (lastInsertedCObj) cobjs[lastInsertedCObj].next = res; else firstInsertedCObj = res;
183   cobj.tag = 0;
184   cobj.prev = lastInsertedCObj;
185   cobj.next = 0;
186 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
187   cobj.updateScheduled = false;
188 #endif
189   cobj.objectIsOwned = ownObjects;
190   lastInsertedCObj = res;
191   return 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;
206   }
207   if (idx == firstInsertedCObj) {
208     if (cobj.prev != 0) FatalError("EntityGrid::freeCObj: invalid alloced list!");
209     firstInsertedCObj = cobj.next;
210   }
211   MapEntity e = cobj.e;
212   if (cobj.objectIsOwned) {
213     delete e;
214   } else if (e) {
215     e.gridId = 0;
216     e.grid = none;
217   }
218   cobj.e = none;
219   cobj.prev = -1;
220   cobj.next = freeHeadCObj;
221 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
222   cobj.updateScheduled = false;
223 #endif
224   cobj.objectIsOwned = false;
225   freeHeadCObj = idx;
229 private final int allocItem () {
230   int res = freeHead;
231   if (res) {
232     freeHead = items[res].next;
233   } else {
234     res = items.length;
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;
238     items[$-1].next = 0;
239     freeHead = res+1;
240   }
241   // other fields will be set by caller
242   return res;
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!");
249   items[idx].cid = -1;
250   items[idx].next = freeHead;
251   freeHead = idx;
255 // ////////////////////////////////////////////////////////////////////////// //
256 private final void insertIntoCell (int cid, int gx, int gy) {
257   int iid = allocItem();
258   Item *it = &items[iid];
259   it.cid = cid;
260   it.next = grid[gx, gy];
261   grid[gx, gy] = iid;
265 private final void removeFromCell (int cid, int gx, int gy) {
266   // find item
267   int iid = grid[gx, gy], iprev = 0;
268   while (iid) {
269     if (items[iid].cid == cid) break;
270     iprev = iid;
271     iid = items[iid].next;
272   }
273   if (iid) {
274     // i found her!
275     int next = items[iid].next;
276     if (iprev) items[iprev].next = next; else grid[gx, gy] = next;
277     freeItem(iid);
278   }
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);
289     }
290   }
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);
301     }
302   }
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) {
316           // old rect: inside
317           // if not inside new, remove
318           if (gx < nx0 || gy < ny0 || gx >= nx1 || gy >= ny1) {
319             removeFromCell(cid, gx, gy);
320           }
321         } else {
322           // old rec: outside
323           // if inside new, insert
324           if (gx >= nx0 && gy >= ny0 && gx < nx1 && gy < ny1) {
325             insertIntoCell(cid, gx, gy);
326           }
327         }
328       }
329     }
330     //removeInternal(cid);
331     cobj.gx0 = nx0; cobj.gy0 = ny0;
332     cobj.gx1 = nx1; cobj.gy1 = ny1;
333     //insertInternal(cid);
334     return true;
335   }
336   return false;
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
353   // calc grid coords
354   ex0 /= CellSize;
355   ey0 /= CellSize;
356   ex1 /= CellSize;
357   ey1 /= CellSize;
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) {
369   if (!e) return 0;
370   if (e.gridId) {
371     if (e.grid != self) FatalError("cannot instert entity in two grids");
372     if (!update(e.gridId)) { e.gridId = 0; e.grid = none; }
373     return e.gridId;
374   }
375   int gx0, gy0, gx1, gy1;
376   bool doInsert = calcObjGridPos(e, out gx0, out gy0, out gx1, out gy1);
377   /*
378   if (e isa MapTileRope) {
379     writeln("ROPE; pos=(", e.x0, ",", e.y0, "); size=(", e.width, "x", e.height, "); grid=(", gx0, ",", gy0, ")-(", gx1, ",", gy1, ")");
380   }
381   */
382   /*
383   if (e isa TitleTileCopy) {
384     writeln("ROPE; pos=(", e.x0, ",", e.y0, "); size=(", e.width, "x", e.height, "); grid=(", gx0, ",", gy0, ")-(", gx1, ",", gy1, ")");
385   }
386   */
387   // allocate cobj
388   int cid = allocCObj();
389   auto cobj = &cobjs[cid];
390   e.gridId = cid;
391   e.grid = self;
392   cobj.e = e;
393   cobj.tag = 0;
394   cobj.gx0 = gx0; cobj.gy0 = gy0;
395   cobj.gx1 = gx1; cobj.gy1 = gy1;
396   if (doInsert) insertInternal(cid);
397   return 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;
405   removeInternal(cid);
406   freeCObj(cid);
407 #ifdef ENTITY_GRID_HAS_SCHEDULED_UPDATES
408   foreach (int idx, int v; updateSchedule) {
409     if (v == cid) {
410       updateSchedule.remove(idx);
411       break;
412     }
413   }
414 #endif
415   return true;
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;
427   if (!e) {
428     // remove it
429     cobjs[cid].updateScheduled = true;
430     updateSchedule[$] = cid;
431     return false;
432   }
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) {
436     // update it
437     cobjs[cid].updateScheduled = true;
438     updateSchedule[$] = cid;
439   }
440   return true;
444 final void performUpdates () {
445   while (updateSchedule.length) {
446     int cid = updateSchedule[$-1];
447     updateSchedule.length -= 1;
448     update(cid);
449   }
451 #endif
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;
468 #endif
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();
473     removeInternal(cid);
474     cobj.gx0 = 0; cobj.gy0 = 0;
475     cobj.gx1 = 0; cobj.gy1 = 0;
476     return false;
477   }
478   wasMoved = moveInternal(cid, gx0, gy0, gx1, gy1);
479   return true;
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) {
494     xcount = 0;
495     ycount = 0;
496   } else {
497     int x1 = x+w-1;
498     int y1 = y+h-1;
499     snapXY(x, y);
500     snapXY(x1, y1);
501     xcount = (x1-x)/CellSize+1;
502     ycount = (y1-y)/CellSize+1;
503   }
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;
514   return cc(cobj.e);
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;
536   return cc(e);
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];
546   while (oid) {
547     // check tag
548     if (specified_tag) {
549       CellObject *co = &cobjs[items[oid].cid];
550       if (co.tag == tag) {
551         //writeln("***TAG SKIP (0)");
552         oid = items[oid].next;
553         continue;
554       }
555       co.tag = tag; // mark it
556     }
557     return oid;
558   }
559   return 0;
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
574   while (oid) {
575     // check tag
576     if (specified_tag) {
577       CellObject *co = &cobjs[items[oid].cid];
578       if (co.tag == tag) {
579         //writeln("***TAG SKIP (1)");
580         oid = items[oid].next;
581         continue;
582       }
583       co.tag = tag; // mark it
584     }
585     return oid;
586   }
587   return 0;
591 // ////////////////////////////////////////////////////////////////////////// //
592 struct CellIter {
593   int oid;
594   int tag;
595   int px, py;
596   bool precise;
597   bool hasTag;
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);
603   while (oid) {
604     MapEntity e = getObjectByItem(objectType, oid);
605     if (e && (!precise || e.isPointCollision(x, y))) {
606       it.oid = oid;
607       it.px = x;
608       it.py = y;
609       it.tag = tag;
610       it.precise = precise;
611       it.hasTag = specified_tag;
612       return true;
613     }
614     oid = getNextItem(oid, tag!optional);
615   }
616   return false;
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;
623   int tag = it.tag;
624   while (oid) {
625     MapEntity e = getObjectByItem(objectType, oid);
626     oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
627     if (e && (!precise || e.isPointCollision(x, y))) {
628       it.oid = oid;
629       ge = e;
630       return true;
631     }
632   }
633   return false;
637 // ////////////////////////////////////////////////////////////////////////// //
638 struct CellRectIter {
639   int cx0, cx1;
640   int cy0, cy1;
641   int cx, cy;
642   int x0, y0, w, h;
643   int oid;
644   int tag;
645   bool precise;
646   bool hasTag;
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);
664       while (oid) {
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, ")");
668           it.oid = oid;
669           it.cx = cx; it.cy = cy;
670           it.cx0 = cx0; it.cy0 = cy0;
671           it.cx1 = cx1; it.cy1 = cy1;
672           it.tag = tag;
673           it.hasTag = specified_tag;
674           it.precise = precise;
675           return true;
676         }
677         oid = getNextItem(oid, tag!optional);
678       }
679     }
680   }
681   return false;
684 final bool inRectPix_opIterNext (ref CellRectIter it, out MapEntity ge) {
685   int oid = it.oid;
686   bool hasTag = it.hasTag;
687   int tag = it.tag;
688   int ix = it.x0, iy = it.y0, iw = it.w, ih = it.h;
689   bool precise = it.precise;
690   while (oid) {
691     MapEntity e = getObjectByItem(objectType, oid);
692     oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
693     if (e && (!precise || e.isRectCollision(ix, iy, iw, ih))) {
694       ge = e;
695       it.oid = oid;
696       return true;
697     }
698   }
699   // move to the next cell
700   ++it.cx;
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));
704       while (oid) {
705         MapEntity e = getObjectByItem(objectType, oid);
706         oid = (hasTag ? getNextItem(oid, tag) : getNextItem(oid));
707         if (e && (!precise || e.isRectCollision(ix, iy, iw, ih))) {
708           ge = e;
709           it.oid = oid;
710           it.cx = cx;
711           it.cy = cy;
712           return true;
713         }
714       }
715     }
716     it.cx = it.cx0;
717   }
718   it.oid = 0;
719   return false;
723 // ////////////////////////////////////////////////////////////////////////// //
724 // 0: no objects
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;
731   if (removeThis) {
732     removeInternal(cid);
733     freeCObj(cid);
734   }
735   return n;
739 // 0: no objects
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;
746   if (removeThis) {
747     removeInternal(cid);
748     freeCObj(cid);
749   }
750   return n;
754 // ////////////////////////////////////////////////////////////////////////// //
755 final bool allObjects_opIterInit (ref int cid) {
756   cid = firstInsertedCObj;
757   while (cid && !cobjs[cid].e) cid = cobjs[cid].next;
758   return (cid != 0);
761 final bool allObjects_opIterNext (ref int cid, out MapEntity ge) {
762   if (!cid) return false;
763   while (cid) {
764     ge = cobjs[cid].e;
765     cid = cobjs[cid].next;
766     if (ge) return true;
767   }
768   return true;
772 final bool allObjectsBackwards_opIterInit (ref int cid) {
773   cid = lastInsertedCObj;
774   while (cid && !cobjs[cid].e) cid = cobjs[cid].prev;
775   return (cid != 0);
778 final bool allObjectsBackwards_opIterNext (ref int cid, out MapEntity ge) {
779   while (cid) {
780     ge = cobjs[cid].e;
781     cid = cobjs[cid].prev;
782     if (ge) return true;
783   }
784   return true;
788 // ////////////////////////////////////////////////////////////////////////// //
790 private static final void siftDownCIDSort (ref array!int arr, int start, int end, bool delegate (int cida, int cidb) dgLess) {
791   auto root = start;
792   for (;;) {
793     auto child = 2*root+1; // left child
794     if (child > end) break;
795     auto swap = root;
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];
801     arr[root] = tmp;
802     root = swap;
803   }
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
811   if (count == 2) {
812     if (dgLess(arr[1], arr[0])) {
813       auto t = arr[0];
814       arr[0] = arr[1];
815       arr[1] = t;
816     }
817     return;
818   }
820   auto end = count-1;
822   // heapify
823   auto start = (end-1)/2; // parent; always safe, as our array has at least two items
824   for (;;) {
825     //siftDownCIDSort(arr, start, end, dgLess);
826     auto root = start;
827     for (;;) {
828       auto child = 2*root+1; // left child
829       if (child > end) break;
830       auto swap = root;
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];
836       arr[root] = tmp;
837       root = swap;
838     }
839     if (start-- == 0) break; // as `start` cannot be negative, use this condition
840   }
842   while (end > 0) {
843     auto tmp = arr[0];
844     arr[0] = arr[end];
845     arr[end] = tmp;
846     --end;
847     //siftDownCIDSort(arr, 0, end, dgLess);
848     auto root = 0;
849     for (;;) {
850       auto child = 2*root+1; // left child
851       if (child > end) break;
852       auto swap = root;
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;
856       tmp = arr[swap];
857       arr[swap] = arr[root];
858       arr[root] = tmp;
859       root = swap;
860     }
861   }
866 private static final void siftDownObjSort (ref array!MapEntity arr, int start, int end, bool delegate (MapEntity a, MapEntity b) dgLess) {
867   auto root = start;
868   for (;;) {
869     auto child = 2*root+1; // left child
870     if (child > end) break;
871     auto swap = root;
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];
877     arr[root] = tmp;
878     root = swap;
879   }
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
887   if (count == 2) {
888     if (dgLess(arr[1], arr[0])) {
889       auto t = arr[0];
890       arr[0] = arr[1];
891       arr[1] = t;
892     }
893     return;
894   }
896   auto end = count-1;
898   // heapify
899   auto start = (end-1)/2; // parent; always safe, as our array has at least two items
900   for (;;) {
901     //siftDownObjSort(arr, start, end, dgLess);
902     auto root = start;
903     for (;;) {
904       auto child = 2*root+1; // left child
905       if (child > end) break;
906       auto swap = root;
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];
912       arr[root] = tmp;
913       root = swap;
914     }
915     if (start-- == 0) break; // as `start` cannot be negative, use this condition
916   }
918   while (end > 0) {
919     auto tmp = arr[0];
920     arr[0] = arr[end];
921     arr[end] = tmp;
922     --end;
923     //siftDownObjSort(arr, 0, end, dgLess);
924     auto root = 0;
925     for (;;) {
926       auto child = 2*root+1; // left child
927       if (child > end) break;
928       auto swap = root;
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;
932       tmp = arr[swap];
933       arr[swap] = arr[root];
934       arr[root] = tmp;
935       root = swap;
936     }
937   }
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
955   int tag;
956   // in current cell, sorted backwards, so we can use [$-1] and decrement length
957   array!int cidlist;
961 // from the right-bottom to left-up
962 final void getAllObjectsBackSorted (ref array!int cidlist, optional bool includeAll) {
963   cidlist.clear();
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;
967   }
968   sortCIDList(cidlist, &cmpSortXYRev);
973 final bool allCellsBackwards_opIterInit (ref CellItemIter it) {
974   it.cidlist.length -= it.cidlist.length; // just in case
975   int tag = nextTag();
976   foreach (int gy; 0..mHeight; backward) {
977     foreach (int gx; 0..mWidth; backward) {
978       int oid = getFirstInCell(gx, gy, tag);
979       while (oid) {
980         MapEntity e = getObject(MapEntity, oid);
981         if (e) it.cidlist[$] = items[oid].cid;
982         oid = getNext(oid, tag);
983       }
984       if (it.cidlist.length) {
985         sortCIDList(it.cidlist, &cmpSortXYRev);
986         it.gx = gx;
987         it.gy = gy;
988         it.tag = tag;
989         return true;
990       }
991     }
992   }
993   return false;
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;
1000     ge = cobjs[cid].e;
1001     if (ge) return true;
1002   }
1003   // next grid cell
1004   --it.gx;
1005   int tag = it.tag;
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);
1009       while (oid) {
1010         MapEntity e = getObject(MapEntity, oid);
1011         if (e) it.cidlist[$] = items[oid].cid;
1012         oid = getNext(oid, tag);
1013       }
1014       if (it.cidlist.length) {
1015         sortCIDList(it.cidlist, &cmpSortXYRev);
1016         it.gx = gx;
1017         it.gy = gy;
1018         int cid = it.cidlist[$-1];
1019         it.cidlist.length -= 1;
1020         ge = cobjs[cid].e;
1021         return true;
1022       }
1023     }
1024     it.gx = mWidth-1;
1025   }
1026   return false;
1031 // ////////////////////////////////////////////////////////////////////////// //
1032 // some debug shit
1033 final int countObjects () {
1034   int objCount = 0;
1035   for (int f = firstInsertedCObj; f; f = cobjs[f].next) ++objCount;
1036   return 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) {
1044     if (!oid) {
1045       ++freeCells;
1046     } else {
1047       int xc = 0;
1048       while (oid) {
1049         ++xc;
1050         oid = items[oid].next;
1051       }
1052       itemCount += xc;
1053       maxInCell = max(maxInCell, xc);
1054     }
1055   }
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 // ////////////////////////////////////////////////////////////////////////// //
1069 struct LineWalker {
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
1075   int horiz; // bool
1077   alias x = xd;
1078   alias y = yd;
1082 // call `lwSetup()` after this
1083 static final void lwCreate (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
1084   // clip rectange
1085   lw.wx0 = minx;
1086   lw.wy0 = miny;
1087   lw.wx1 = maxx;
1088   lw.wy1 = maxy;
1092 static final void lwSetClip (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
1093   // clip rectange
1094   lw.wx0 = minx;
1095   lw.wy0 = miny;
1096   lw.wx1 = maxx;
1097   lw.wy1 = 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; }
1107   bool res;
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)
1110   {
1111     res = true;
1112   } else {
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);
1119   }
1121   // check for ortho lines
1122   if (y0 == y1) {
1123     // horizontal
1124     lw.horiz = true;
1125     lw.stleft = abs(x1-x0)+1;
1126     lw.stx = (x0 < x1 ? 1 : -1);
1127     lw.sty = 0;
1128     lw.errinc = 0;
1129     lw.errmax = 10; // anything that is greater than zero
1130   } else if (x0 == x1) {
1131     // vertical
1132     lw.horiz = false;
1133     lw.stleft = abs(y1-y0)+1;
1134     lw.stx = 0;
1135     lw.sty = (y0 < y1 ? 1 : -1);
1136     lw.errinc = 0;
1137     lw.errmax = 10; // anything that is greater than zero
1138   } else {
1139     // diagonal
1140     if (abs(x1-x0) >= abs(y1-y0)) {
1141       // horizontal
1142       lw.horiz = true;
1143       lw.stleft = abs(x1-x0)+1;
1144       lw.errinc = abs(y1-y0)+1;
1145     } else {
1146       // vertical
1147       lw.horiz = false;
1148       lw.stleft = abs(y1-y0)+1;
1149       lw.errinc = abs(x1-x0)+1;
1150     }
1151     lw.stx = (x0 < x1 ? 1 : -1);
1152     lw.sty = (y0 < y1 ? 1 : -1);
1153     lw.errmax = lw.stleft;
1154   }
1155   lw.xd = x0;
1156   lw.yd = y0;
1157   lw.err = -lw.errmax;
1158   return res;
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) {
1172   if (lw.horiz) {
1173     lw.xd += lw.stx;
1174     lw.err += lw.errinc;
1175     if (lw.err >= 0) { lw.err -= lw.errmax; lw.yd += lw.sty; }
1176   } else {
1177     lw.yd += lw.sty;
1178     lw.err += lw.errinc;
1179     if (lw.err >= 0) { lw.err -= lw.errmax; lw.xd += lw.stx; }
1180   }
1181   --lw.stleft;
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
1190   int ex, ey;
1192   // strictly horizontal?
1193   if (lw.sty == 0) {
1194     // only xd
1195     if (lw.stx < 0) {
1196       // xd: to left edge
1197       ex = (lw.xd&(~(CellSize-1)))-1;
1198       lw.stleft -= lw.xd-ex;
1199     } else {
1200       // xd: to right edge
1201       ex = (lw.xd|(CellSize-1))+1;
1202       lw.stleft -= ex-lw.xd;
1203     }
1204     lw.xd = ex;
1205     return (lw.stleft <= 0);
1206   }
1208   // strictly vertical?
1209   if (lw.stx == 0) {
1210     // only xd
1211     if (lw.sty < 0) {
1212       // yd: to top edge
1213       ey = (lw.yd&(~(CellSize-1)))-1;
1214       lw.stleft -= lw.yd-ey;
1215     } else {
1216       // yd: to bottom edge
1217       ey = (lw.yd|(CellSize-1))+1;
1218       lw.stleft -= ey-lw.yd;
1219     }
1220     lw.yd = ey;
1221     return (lw.stleft <= 0);
1222   }
1224   // diagonal
1225   int xwalk, ywalk;
1227   // calculate xwalk
1228   if (lw.stx < 0) {
1229     ex = (lw.xd&(~(CellSize-1)))-1;
1230     xwalk = lw.xd-ex;
1231   } else {
1232     ex = (lw.xd|(CellSize-1))+1;
1233     xwalk = ex-lw.xd;
1234   }
1236   // calculate ywalk
1237   if (lw.sty < 0) {
1238     ey = (lw.yd&(~(CellSize-1)))-1;
1239     ywalk = lw.yd-ey;
1240   } else {
1241     ey = (lw.yd|(CellSize-1))+1;
1242     ywalk = ey-lw.yd;
1243   }
1245   int wklen = (xwalk <= ywalk ? xwalk : ywalk);
1246   while (true) {
1247     // in which dir we want to walk?
1248     lw.stleft -= wklen;
1249     if (lw.stleft <= 0) return true;
1250     if (lw.horiz) {
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; }
1255       }
1256     } else {
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; }
1261       }
1262     }
1263     // check for walk completion
1264     if (lw.xd == ex || lw.yd == ey) break;
1265     wklen = 1;
1266   }
1268   return false;