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;
22 int tag; // arbitrary userdata, set to 0 in new items
23 // last known grid position
24 int gx0, gy0, gx1, gy1;
25 // previous item in alloced list
27 // next item in free/alloced list
29 bool objectIsOwned; // will be `delete`d on removing
33 int cid; // index in `cobjs`
35 // otherwise either next item in cell, or next item in free list
40 private array!CellObject cobjs;
41 private int freeHeadCObj;
42 private int firstInsertedCObj; // for `allObjects()`
43 private int lastInsertedCObj; // for `allObjects()`
45 // item #0 is never used
46 private array!Item items;
49 //WARNING! don't change
51 private int mWidth, mHeight;
52 // offset of the (0, 0) grid cell, in pixels
53 private int mXOfs, mYOfs;
55 // grid cells, width*height in total
56 // contains index in `items` or 0
57 private array!int grid;
59 private transient int lastUsedTag;
61 bool ownObjects; // should we own and `delete` inserted objects?
63 // this is used in various grid queries
64 transient private array!MapEntity collected;
66 transient int collUsed; // used items in `collected`
70 final int gridX0 { get mXOfs; }
71 final int gridY0 { get mYOfs; }
72 final int gridX1 { get { return mXOfs+mWidth*CellSize-1; } }
73 final int gridY1 { get { return mYOfs+mHeight*CellSize-1; } }
74 final int gridWidth { get { return mWidth*CellSize; } }
75 final int gridHeight { get { return mHeight*CellSize; } }
78 // ////////////////////////////////////////////////////////////////////////// //
79 final void setup (int x0, int y0, int awidth, int aheight) {
80 awidth = max(1, awidth);
81 aheight = max(1, aheight);
82 int xcells = max(1, (awidth+CellSize-1)/CellSize);
83 int ycells = max(1, (aheight+CellSize-1)/CellSize);
90 // setup items and free list
93 grid.setSize(xcells, ycells);
99 if (collUsed) FatalError("EntityGrid::clear(): query in progress");
100 if (lastSafeIter) FatalError("EntityGrid::clear(): safe query in progress");
111 collected.length = 0;
115 final void removeAllObjects (optional bool doRemove) {
116 if (collUsed) FatalError("EntityGrid::removeAllObjects(): query in progress");
117 if (lastSafeIter) FatalError("EntityGrid::clear(): safe query in progress");
120 foreach (ref auto oid; grid) oid = 0;
123 foreach (int f, ref auto item; items) item.next = f+1;
124 items[0].next = 0; // as sentinel
128 for (int f = firstInsertedCObj; f; f = cobjs[f].next) {
129 if (doRemove || cobjs[f].objectIsOwned) delete cobjs[f].e; else cobjs[f].e = none;
132 foreach (int f, ref auto cobj; cobjs) {
137 cobj.objectIsOwned = false;
139 cobjs[0].next = 0; // as sentinel
142 firstInsertedCObj = 0;
143 lastInsertedCObj = 0;
144 collected.clear(); // just in case
148 private final void clearTags () {
149 for (int oid = firstInsertedCObj; oid; oid = cobjs[oid].next) {
155 private final int nextTag () {
156 if (lastUsedTag >= 0x3fff_ffff && collUsed == 0) {
160 if (lastUsedTag == 0x7fff_ffff) FatalError("EntityGrid: tag overflow");
161 return ++lastUsedTag;
165 // ////////////////////////////////////////////////////////////////////////// //
166 private final int allocCObj () {
167 int res = freeHeadCObj;
169 freeHeadCObj = cobjs[res].next;
172 int newlen = (res > 3 ? res+res/2 : 8);
173 cobjs.length = newlen;
174 foreach (int f; res+1..newlen) { cobjs[f].prev = -1; cobjs[f].next = f+1; }
176 freeHeadCObj = res+1;
178 // other fields will be set by caller
179 auto cobj = &cobjs[res];
180 if (lastInsertedCObj) cobjs[lastInsertedCObj].next = res; else firstInsertedCObj = res;
182 cobj.prev = lastInsertedCObj;
184 cobj.objectIsOwned = ownObjects;
185 lastInsertedCObj = res;
190 private final void freeCObj (int idx) {
191 if (idx < 1 || idx >= cobjs.length) FatalError("EntityGrid::freeCObj: invalid item index!");
192 auto cobj = &cobjs[idx];
193 if (cobj.prev == -1) FatalError("EntityGrid::freeCObj: double free!");
194 // fix safe iterators
195 for (AllObjSafeIterData *it = lastSafeIter; it; it = it.next) {
197 // move to the next one
198 it.cid = cobjs[it.cid].next;
201 // remove us from the alloced list
202 if (cobj.prev) cobjs[cobj.prev].next = cobj.next;
203 if (cobj.next) cobjs[cobj.next].prev = cobj.prev;
204 // fix alloced list (and do some sanity checks)
205 if (idx == lastInsertedCObj) {
206 if (cobj.next != 0) FatalError("EntityGrid::freeCObj: invalid alloced list!");
207 lastInsertedCObj = cobj.prev;
209 if (idx == firstInsertedCObj) {
210 if (cobj.prev != 0) FatalError("EntityGrid::freeCObj: invalid alloced list!");
211 firstInsertedCObj = cobj.next;
213 MapEntity e = cobj.e;
214 if (cobj.objectIsOwned) {
222 cobj.next = freeHeadCObj;
223 cobj.objectIsOwned = false;
228 private final int allocItem () {
231 freeHead = items[res].next;
234 int newlen = (res > 3 ? res+res/2 : 8);
235 items.length = newlen;
236 foreach (int f; res+1..newlen) items[f].next = f+1;
240 // other fields will be set by caller
245 private final void freeItem (int idx) {
246 if (idx < 1 || idx >= items.length) FatalError("EntityGrid::freeItem: invalid item index!");
247 if (items[idx].cid == -1) FatalError("EntityGrid::freeItem: double free!");
249 items[idx].next = freeHead;
254 // ////////////////////////////////////////////////////////////////////////// //
255 private final void insertIntoCell (int cid, int gx, int gy) {
256 int iid = allocItem();
257 Item *it = &items[iid];
259 it.next = grid[gx, gy];
264 private final void removeFromCell (int cid, int gx, int gy) {
266 int iid = grid[gx, gy], iprev = 0;
268 if (items[iid].cid == cid) break;
270 iid = items[iid].next;
274 int next = items[iid].next;
275 if (iprev) items[iprev].next = next; else grid[gx, gy] = next;
281 // `cobjs[cid]` should be fully initialized
282 private final void insertInternal (int cid) {
283 auto cobj = &cobjs[cid];
284 int gx0 = cobj.gx0, gx1 = cobj.gx1;
285 foreach (int cy; cobj.gy0..cobj.gy1) {
286 foreach (int cx; gx0..gx1) {
287 insertIntoCell(cid, cx, cy);
293 // this won't free `cid`
294 private final void removeInternal (int cid) {
295 auto cobj = &cobjs[cid];
296 int gx0 = cobj.gx0, gx1 = cobj.gx1;
297 foreach (int cy; cobj.gy0..cobj.gy1) {
298 foreach (int cx; gx0..gx1) {
299 removeFromCell(cid, cx, cy);
305 // returns `true` if moved
306 private final bool moveInternal (int cid, int nx0, int ny0, int nx1, int ny1) {
307 auto cobj = &cobjs[cid];
308 int gx0 = cobj.gx0, gy0 = cobj.gy0;
309 int gx1 = cobj.gx1, gy1 = cobj.gy1;
310 if (gx0 != nx0 || gy0 != ny0 || gx1 != nx1 || gy1 != ny1) {
311 foreach (int gy; min(gy0, ny0)..max(gy1, ny1)) {
312 foreach (int gx; min(gx0, nx0)..max(gx1, nx1)) {
313 // are we inside the old rect?
314 if (gx >= gx0 && gy >= gy0 && gx < gx1 && gy < gy1) {
316 // if not inside new, remove
317 if (gx < nx0 || gy < ny0 || gx >= nx1 || gy >= ny1) {
318 removeFromCell(cid, gx, gy);
322 // if inside new, insert
323 if (gx >= nx0 && gy >= ny0 && gx < nx1 && gy < ny1) {
324 insertIntoCell(cid, gx, gy);
329 //removeInternal(cid);
330 cobj.gx0 = nx0; cobj.gy0 = ny0;
331 cobj.gx1 = nx1; cobj.gy1 = ny1;
332 //insertInternal(cid);
339 // `false`: out of grid/too thin/etc
340 // returned coords are always valid
341 // also, `gx1` and `gy1` are exclusive
342 final bool calcObjGridPos (MapEntity e, out int gx0, out int gy0, out int gx1, out int gy1) {
343 if (!e) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // no object
344 int ew = max(1, e.width), eh = max(1, e.height);
345 //if (ew < 1 || eh < 1) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; }
346 int ex0 = e.x0-mXOfs, ey0 = e.y0-mYOfs;
347 int ex1 = ex0+ew-1, ey1 = ey0+eh-1;
348 // check if it fits in grid at all
349 if (ex1 < 0 || ey1 < 0) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // out of grid
350 int tgw = mWidth, tgh = mHeight;
351 if (ex0 >= tgw*CellSize || ey0 >= tgh*CellSize) { gx0 = 0; gy0 = 0; gx1 = 0; gy1 = 0; return false; } // out of grid
357 gx0 = clamp(ex0, 0, tgw-1);
358 gy0 = clamp(ey0, 0, tgh-1);
359 gx1 = clamp(ex1+1, 0, tgw);
360 gy1 = clamp(ey1+1, 0, tgh);
361 return (gx1 > gx0 && gy1 > gy0);
365 // ////////////////////////////////////////////////////////////////////////// //
366 // returns 0 if item is not put in grid, or cid
367 final int insert (MapEntity e) {
370 if (e.grid != self) FatalError("cannot instert entity in two grids");
371 if (!update(e.gridId)) { e.gridId = 0; e.grid = none; }
374 int gx0, gy0, gx1, gy1;
375 bool doInsert = calcObjGridPos(e, out gx0, out gy0, out gx1, out gy1);
377 int cid = allocCObj();
378 auto cobj = &cobjs[cid];
383 cobj.gx0 = gx0; cobj.gy0 = gy0;
384 cobj.gx1 = gx1; cobj.gy1 = gy1;
385 if (doInsert) insertInternal(cid);
390 // returns `true` if item was in the grid, and now removed
391 final bool remove (int cid) {
392 if (cid < 1 || cid >= cobjs.length) return false;
393 if (cobjs[cid].prev == -1) return false;
400 // call this when object changed its position
401 // it is harmless to call it even if position wasn't changed
402 // returns `false` if item should be removed from grid
403 // optionally sets `wasMoved` flag
404 final bool update (int cid, optional out bool wasMoved/*, optional bool markAsDead*/) {
405 if (specified_wasMoved) wasMoved = false;
406 if (cid < 1 || cid >= cobjs.length) return false;
407 auto cobj = &cobjs[cid];
408 if (cobj.prev == -1) return false;
409 MapEntity e = cobj.e;
410 if (!e) return false;
411 int gx0, gy0, gx1, gy1;
412 if (!calcObjGridPos(e, out gx0, out gy0, out gx1, out gy1)) {
413 //if (markAsDead && !e.isZeroDimsValid && (e.width < 1 || e.height < 1)) e.instanceRemove();
415 cobj.gx0 = 0; cobj.gy0 = 0;
416 cobj.gx1 = 0; cobj.gy1 = 0;
419 wasMoved = moveInternal(cid, gx0, gy0, gx1, gy1);
424 // ////////////////////////////////////////////////////////////////////////// //
425 // ok, it is not a real spawner, i just wanted to fake its return type
426 final spawner MapEntity getObject (class!MapEntity cc, int cid) {
427 if (cid < 1 || cid >= cobjs.length) return none;
428 auto cobj = &cobjs[cid];
429 if (cobj.prev == -1) return none;
433 final bool getOwned (int cid) {
434 if (cid < 1 || cid >= cobjs.length) return false;
435 auto cobj = &cobjs[cid];
436 return cobj.objectIsOwned;
439 final void setOwned (int cid, bool v) {
440 if (cid < 1 || cid >= cobjs.length) return;
441 auto cobj = &cobjs[cid];
442 if (cobj.prev != -1) cobj.objectIsOwned = v;
446 // ////////////////////////////////////////////////////////////////////////// //
447 // ok, it is not a real spawner, i just wanted to fake its return type
448 final spawner MapEntity getObjectByItem (class!MapEntity cc, int id) {
449 if (id < 1 || id >= items.length) return none;
450 MapEntity e = cobjs[items[id].cid].e;
451 if (!e || !e.isInstanceAlive) return none;
456 // returns first item in cell, optionally tags it too (and skips the given tag)
457 // returns 0 if there are no items
458 final int getFirstItemInCell (int cx, int cy, optional int tag) {
459 //writeln("specified_tag=", specified_tag, "; tag=", tag);
460 if (cx < 0 || cy < 0 || cx >= mWidth || cy >= mHeight) return 0;
461 int oid = grid[cx, cy];
465 CellObject *co = &cobjs[items[oid].cid];
467 //writeln("***TAG SKIP (0)");
468 oid = items[oid].next;
471 co.tag = tag; // mark it
479 // returns first item in cell, optionally tags it too (and skips the given tag)
480 // returns 0 if there are no items
481 final int getFirstItem (int x, int y, optional int tag) {
482 return getFirstItemInCell((x-mXOfs)/CellSize, (y-mYOfs)/CellSize, tag!optional);
486 // returns next item in list, optionally tags it too (and skips the given tag)
487 // returns 0 if there are no more items
488 final int getNextItem (int oid, optional int tag) {
489 oid = items[oid].next; // 0 contains sentinel anyway
493 CellObject *co = &cobjs[items[oid].cid];
495 //writeln("***TAG SKIP (1)");
496 oid = items[oid].next;
499 co.tag = tag; // mark it
507 // ////////////////////////////////////////////////////////////////////////// //
508 // low-level iterator builder
509 // `GridIteratorData` contents doesn't matter
510 private final void collectObjectsInRect (out GridIteratorData idata, int ax, int ay, int aw, int ah, bool precise, class!MapEntity castClass,
511 scope bool delegate (MapEntity e, int x0, int y0, int width, int height) cdg)
513 int cused = collUsed;
514 idata.collIdx = cused;
515 idata.collStart = cused;
516 idata.collEnd = cused;
517 if (aw < 1 || ah < 1) return;
518 int grX = ax-mXOfs, grY = ay-mYOfs;
519 if (grX+aw <= 0 || grY+ah <= 0 || grX >= mWidth*CellSize || grY >= mHeight*CellSize) return;
520 int cx0 = max(0, grX/CellSize);
521 int cy0 = max(0, grY/CellSize);
522 int cx1 = min(mWidth, (grX+aw-1)/CellSize+1);
523 int cy1 = min(mHeight, (grY+ah-1)/CellSize+1);
524 auto tag = nextTag(); // the tag will be used to reject duplicate objects
525 //writeln("(", ax, ",", ay, ")-(", ax+aw-1, ",", ay+ah-1, "): (", cx0, ",", cy0, ")-(", cx1, ",", cy1, ")");
526 foreach (int cy; cy0..cy1) {
527 foreach (int cx; cx0..cx1) {
528 int oid = getFirstItemInCell(cx, cy, tag);
530 MapEntity e = getObjectByItem(castClass, oid); // `e` is guaranteed to be valid or `none`
531 if (e isa castClass) {
532 if (!precise || cdg(e, ax, ay, aw, ah)) collected[cused++] = e;
534 oid = getNextItem(oid, tag);
538 idata.castClass = castClass;
539 idata.collEnd = cused;
544 // ////////////////////////////////////////////////////////////////////////// //
545 // high-level iterators
546 // you can add/remove objects while iterating, it is harmless
547 // newly added objects may, or may not participate in current frame processing
548 // removed objects will be returned unless they are marked dead with `obj.instanceRemove()`
550 struct GridIteratorData {
551 // positions in `collected`
552 int collIdx; // current index
553 int collStart; // first item for this iterator
554 int collEnd; // last (exclusive) item for this iterator
555 class!MapEntity castClass;
559 // ////////////////////////////////////////////////////////////////////////// //
561 final private bool pixelCheckerDG (MapEntity e, int x0, int y0, int width, int height) {
562 return e.isPointCollision(x0, y0);
566 // collect objects at a given pixel
567 // if `precise` is `false`, collect objects with cell granularity
568 final bool inCellPix_opIterInit (ref GridIteratorData it, int x, int y,
569 optional bool precise, optional class!MapEntity castClass)
571 if (!specified_precise) precise = true;
572 if (!castClass) castClass = MapEntity;
573 collectObjectsInRect(it, x, y, 1, 1, precise, castClass, &pixelCheckerDG);
574 //writeln("*** ICP_INIT: start=", it.collStart, "; end=", it.collEnd, "; collUsed=", collUsed, "; valid=", (it.collEnd > it.collStart));
575 return (it.collEnd > it.collStart);
579 final bool inCellPix_opIterNext (ref GridIteratorData it, out MapEntity ge) {
580 while (it.collIdx < it.collEnd) {
581 MapEntity e = collected[it.collIdx++];
582 // this object may be marked as dead or `delete`d, so check for this
583 if (e && e.isInstanceAlive) {
588 return false; // this iterator is no more
592 final void inCellPix_opIterDone (ref GridIteratorData it) {
593 //writeln("*** ICP_DONE: start=", it.collStart, "; end=", it.collEnd, "; collUsed=", collUsed, "; new collUsed=", it.collStart);
594 if (it.collEnd > it.collStart) {
595 if (collUsed != it.collEnd) FatalError("EntityGrid::inCellPix: unbalanced iterators");
596 collUsed = it.collStart;
601 // ////////////////////////////////////////////////////////////////////////// //
603 final private bool rectCheckerDG (MapEntity e, int x0, int y0, int width, int height) {
604 return e.isRectCollision(x0, y0, width, height);
608 // collect objects at a given pixel
609 // if `precise` is `false`, collect objects with cell granularity
610 final bool inRectPix_opIterInit (ref GridIteratorData it, int x, int y, int width, int height,
611 optional bool precise, optional class!MapEntity castClass)
613 if (!specified_precise) precise = true;
614 if (!castClass) castClass = MapEntity;
615 collectObjectsInRect(it, x, y, width, height, precise, castClass, &rectCheckerDG);
616 //writeln("*** IRP_INIT: start=", it.collStart, "; end=", it.collEnd, "; collUsed=", collUsed, "; valid=", (it.collEnd > it.collStart));
617 return (it.collEnd > it.collStart);
621 final bool inRectPix_opIterNext (ref GridIteratorData it, out MapEntity ge) {
622 while (it.collIdx < it.collEnd) {
623 MapEntity e = collected[it.collIdx++];
624 // this object may be marked as dead or `delete`d, so check for this
625 if (e && e.isInstanceAlive) {
630 return false; // this iterator is no more
634 final void inRectPix_opIterDone (ref GridIteratorData it) {
635 //writeln("*** IRP_DONE: start=", it.collStart, "; end=", it.collEnd, "; collUsed=", collUsed, "; new collUsed=", it.collStart);
636 if (it.collEnd > it.collStart) {
637 if (collUsed != it.collEnd) FatalError("EntityGrid::inRectPix: unbalanced iterators");
638 collUsed = it.collStart;
643 // ////////////////////////////////////////////////////////////////////////// //
645 final int getFirstObjectCID () { return firstInsertedCObj; }
647 // 0: no more objects
648 final int getNextObjectCID (int cid) {
649 if (cid < 1 || cid >= cobjs.length || cobjs[cid].prev == -1) return 0;
650 int n = cobjs[cid].next;
662 final int getLastObjectCID () { return lastInsertedCObj; }
664 // 0: no more objects
665 final int getPrevObjectCID (int cid) {
666 if (cid < 1 || cid >= cobjs.length || cobjs[cid].prev == -1) return 0;
667 int n = cobjs[cid].prev;
678 // ////////////////////////////////////////////////////////////////////////// //
679 // creates "safe" iterator: you can insert/remove items while iterating
681 transient private AllObjSafeIterData *lastSafeIter;
683 struct AllObjSafeIterData {
684 AllObjSafeIterData *next;
686 class!MapEntity castClass;
689 final bool allObjectsSafe_opIterInit (ref AllObjSafeIterData idata, optional class!MapEntity castClass) {
690 if (!castClass) castClass = MapEntity;
691 int cid = firstInsertedCObj;
693 MapEntity e = cobjs[cid].e;
694 if (e isa castClass && e.isInstanceAlive) {
695 idata.next = lastSafeIter;
696 lastSafeIter = &idata;
698 idata.castClass = castClass;
701 cid = cobjs[cid].next;
703 idata.next = nullptr;
704 idata.cid = -666; // "unused" flag
708 final bool allObjectsSafe_opIterNext (ref AllObjSafeIterData idata, out MapEntity ge) {
710 auto cc = idata.castClass;
712 MapEntity e = cobjs[cid].e;
713 cid = cobjs[cid].next;
714 if (e isa cc && e.isInstanceAlive) {
720 idata.cid = 0; // mark "complete" (just in case)
724 final void allObjectsSafe_opIterDone (ref AllObjSafeIterData idata) {
725 if (idata.cid != -666) {
726 // remove iterator from list
727 if (lastSafeIter != &idata) FatalError("EntityGrid::allObjectsSafe: unbalanced iterators");
728 lastSafeIter = idata.next;
729 idata.next = nullptr;
735 // ////////////////////////////////////////////////////////////////////////// //
736 struct AllObjIterData {
738 class!MapEntity castClass;
741 final bool allObjects_opIterInit (ref AllObjIterData idata, optional class!MapEntity castClass) {
742 if (!castClass) castClass = MapEntity;
743 int cid = firstInsertedCObj;
745 MapEntity e = cobjs[cid].e;
746 if (e isa castClass && e.isInstanceAlive) {
748 idata.castClass = castClass;
751 cid = cobjs[cid].next;
757 final bool allObjects_opIterNext (ref AllObjIterData idata, out MapEntity ge) {
759 auto cc = idata.castClass;
761 MapEntity e = cobjs[cid].e;
762 cid = cobjs[cid].next;
763 if (e isa cc && e.isInstanceAlive) {
769 idata.cid = 0; // mark "complete" (just in case)
774 // ////////////////////////////////////////////////////////////////////////// //
775 // hack for make some iterations (especially frame thinker) slightly faster
776 final void collectAllActiveObjects (ref array!MapEntity aoarr) {
777 int cid = firstInsertedCObj;
779 MapEntity e = cobjs[cid].e;
780 if (e && e.active && e.isInstanceAlive) aoarr[$] = e;
781 cid = cobjs[cid].next;
786 // ////////////////////////////////////////////////////////////////////////// //
788 final bool allObjectsBackwards_opIterInit (ref int cid) {
789 cid = lastInsertedCObj;
790 while (cid && !cobjs[cid].e) cid = cobjs[cid].prev;
794 final bool allObjectsBackwards_opIterNext (ref int cid, out MapEntity ge) {
796 MapEntity e = cobjs[cid].e;
797 cid = cobjs[cid].prev;
798 if (e && e.isInstanceAlive) { ge = e; return true; }
805 // ////////////////////////////////////////////////////////////////////////// //
807 final int countObjects () {
809 for (int f = firstInsertedCObj; f; f = cobjs[f].next) ++objCount;
814 final void dumpStats () {
815 // calculate max object in cell, number of free cells, and number of used items
816 int freeCells = 0, maxInCell = 0, itemCount = 0;
817 foreach (int oid; grid) {
824 oid = items[oid].next;
827 maxInCell = max(maxInCell, xc);
830 int objCount = countObjects();
831 writeln("=========== GRID STATS ===========");
832 writeln("bounds: (", mXOfs, ",", mYOfs, ")-(", mXOfs+mWidth*CellSize-1, ",", mYOfs+mHeight*CellSize-1, ")");
833 writeln("cells: ", grid.length, " (", grid.length1, "x", grid.length2, ")");
834 writeln("objects: ", objCount, " (out of ", cobjs.length, " slots)");
835 writeln("used items: ", itemCount, " out of ", items.length);
836 writeln("max items in cell: ", maxInCell);
837 writeln("used cells: ", grid.length-freeCells, " out of ", grid.length);
838 writeln("----------------------------------");
842 // ////////////////////////////////////////////////////////////////////////// //
845 int wx0, wy0, wx1, wy1; // window coordinates
846 int stx, sty; // "steps" for x and y axes
847 int stleft; // "steps left"
848 int err, errinc, errmax;
849 int xd, yd; // current coord
857 // call `lwSetup()` after this
858 static final void lwCreate (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
867 static final void lwSetClip (ref LineWalker lw, int minx, int miny, int maxx, int maxy) {
876 // this will use `w[xy][01]` to clip coords
877 // return `false` if the whole line was clipped away
878 // on `true`, you should process first point, and go on
879 static final bool lwSetup (ref LineWalker lw, int x0, int y0, int x1, int y1) {
880 if (lw.wx1 < lw.wx0 || lw.wy1 < lw.wy0) { lw.stleft = 0; lw.xd = x0; lw.yd = y0; return false; }
883 if (x0 >= lw.wx0 && y0 >= lw.wy0 && x0 <= lw.wx1 && y0 <= lw.wy1 &&
884 x1 >= lw.wx0 && y1 >= lw.wy0 && x1 <= lw.wx1 && y1 <= lw.wy1)
888 float sx0 = x0, sy0 = y0;
889 float sx1 = x1, sy1 = y1;
890 res = Geom.clipLine(sx0, sy0, sx1, sy1, lw.wx0, lw.wy0, lw.wx1, lw.wy1);
891 if (!res) { lw.stleft = 0; lw.xd = x0; lw.yd = y0; return false; }
892 x0 = trunc(sx0); y0 = trunc(sy0);
893 x1 = trunc(sx1); y1 = trunc(sy1);
896 // check for ortho lines
900 lw.stleft = abs(x1-x0)+1;
901 lw.stx = (x0 < x1 ? 1 : -1);
904 lw.errmax = 10; // anything that is greater than zero
905 } else if (x0 == x1) {
908 lw.stleft = abs(y1-y0)+1;
910 lw.sty = (y0 < y1 ? 1 : -1);
912 lw.errmax = 10; // anything that is greater than zero
915 if (abs(x1-x0) >= abs(y1-y0)) {
918 lw.stleft = abs(x1-x0)+1;
919 lw.errinc = abs(y1-y0)+1;
923 lw.stleft = abs(y1-y0)+1;
924 lw.errinc = abs(x1-x0)+1;
926 lw.stx = (x0 < x1 ? 1 : -1);
927 lw.sty = (y0 < y1 ? 1 : -1);
928 lw.errmax = lw.stleft;
937 // call this *after* doing a step
938 // WARNING! if you will do a step when this returns `true`, you will fall into limbo
939 static final bool lwIsDone (ref LineWalker lw) {
940 return (lw.stleft <= 0);
944 // as you will prolly call `done()` after doing a step anyway, this will do it for you
945 // move to next point, return `true` when the line is complete (i.e. you should stop)
946 static final bool step (ref LineWalker lw) {
950 if (lw.err >= 0) { lw.err -= lw.errmax; lw.yd += lw.sty; }
954 if (lw.err >= 0) { lw.err -= lw.errmax; lw.xd += lw.stx; }
957 return (lw.stleft <= 0);
961 // move to next tile; return `true` if the line is complete (and walker state is undefined then)
962 static final bool stepToNextTile (ref LineWalker lw) {
963 if (lw.stleft < 2) return true; // max one pixel left, nothing to do
967 // strictly horizontal?
972 ex = (lw.xd&(~(CellSize-1)))-1;
973 lw.stleft -= lw.xd-ex;
976 ex = (lw.xd|(CellSize-1))+1;
977 lw.stleft -= ex-lw.xd;
980 return (lw.stleft <= 0);
983 // strictly vertical?
988 ey = (lw.yd&(~(CellSize-1)))-1;
989 lw.stleft -= lw.yd-ey;
991 // yd: to bottom edge
992 ey = (lw.yd|(CellSize-1))+1;
993 lw.stleft -= ey-lw.yd;
996 return (lw.stleft <= 0);
1004 ex = (lw.xd&(~(CellSize-1)))-1;
1007 ex = (lw.xd|(CellSize-1))+1;
1013 ey = (lw.yd&(~(CellSize-1)))-1;
1016 ey = (lw.yd|(CellSize-1))+1;
1020 int wklen = (xwalk <= ywalk ? xwalk : ywalk);
1022 // in which dir we want to walk?
1024 if (lw.stleft <= 0) return true;
1026 lw.xd += wklen*lw.stx;
1027 for (int f = wklen; f > 0; --f) {
1028 lw.err += lw.errinc;
1029 if (lw.err >= 0) { lw.err -= lw.errmax; lw.yd += lw.sty; }
1032 lw.yd += wklen*lw.sty;
1033 for (int f = wklen; f > 0; --f) {
1034 lw.err += lw.errinc;
1035 if (lw.err >= 0) { lw.err -= lw.errmax; lw.xd += lw.stx; }
1038 // check for walk completion
1039 if (lw.xd == ex || lw.yd == ey) break;
1048 // ////////////////////////////////////////////////////////////////////////// //
1049 // unfinished line walker from GameLevel
1050 //FIXME: optimize this with clipping first
1051 //TODO: moveable tiles
1053 final MapTile checkTilesAtLine (int ax0, int ay0, int ax1, int ay1, optional scope bool delegate (MapTile dg) dg) {
1054 // do it faster if we can
1056 // strict vertical check?
1057 if (ax0 == ax1 && ay0 <= ay1) return checkTilesInRect(ax0, ay0, 1, ay1-ay0+1, dg!optional);
1058 // strict horizontal check?
1059 if (ay0 == ay1 && ax0 <= ax1) return checkTilesInRect(ax0, ay0, ax1-ax0+1, 1, dg!optional);
1061 float x0 = float(ax0)/16.0, y0 = float(ay0)/16.0, x1 = float(ax1)/16.0, y1 = float(ay1)/16.0;
1064 if (!dg) dg = &cbCollisionAnySolid;
1066 // get starting and enging tile
1067 int tileSX = trunc(x0), tileSY = trunc(y0);
1068 int tileEX = trunc(x1), tileEY = trunc(y1);
1070 // first hit is always landed
1071 if (tileSX >= 0 && tileSY >= 0 && tileSX < TilesWidth && tileSY < TilesHeight) {
1072 MapTile t = tiles[tileSX, tileSY];
1073 if (t && t.visible && Geom.lineAABBIntersects(ax0, ay0, ax1, ay1, tileSX*16, tileSY*16, 16, 16) && dg(t)) return t;
1076 // if starting and ending tile is the same, we don't need to do anything more
1077 if (tileSX == tileEX && tileSY == tileEY) return none;
1079 // calculate ray direction
1080 TVec dv = (vector(x1, y1)-vector(x0, y0)).normalise2d;
1082 // length of ray from one x or y-side to next x or y-side
1083 float deltaDistX = (fabs(dv.x) > 0.0001 ? fabs(1.0/dv.x) : 0.0);
1084 float deltaDistY = (fabs(dv.y) > 0.0001 ? fabs(1.0/dv.y) : 0.0);
1086 // calculate step and initial sideDists
1088 float sideDistX; // length of ray from current position to next x-side
1089 int stepX; // what direction to step in x (either +1 or -1)
1092 sideDistX = (x0-tileSX)*deltaDistX;
1095 sideDistX = (tileSX+1.0-x0)*deltaDistX;
1098 float sideDistY; // length of ray from current position to next y-side
1099 int stepY; // what direction to step in y (either +1 or -1)
1102 sideDistY = (y0-tileSY)*deltaDistY;
1105 sideDistY = (tileSY+1.0-y0)*deltaDistY;
1109 //int side; // was a NS or a EW wall hit?
1111 // jump to next map square, either in x-direction, or in y-direction
1112 if (sideDistX < sideDistY) {
1113 sideDistX += deltaDistX;
1117 sideDistY += deltaDistY;
1122 if (tileSX >= 0 && tileSY >= 0 && tileSX < TilesWidth && tileSY < TilesHeight) {
1123 MapTile t = tiles[tileSX, tileSY];
1124 if (t && t.visible && Geom.lineAABBIntersects(ax0, ay0, ax1, ay1, tileSX*16, tileSY*16, 16, 16) && dg(t)) return t;
1126 // did we arrived at the destination?
1127 if (tileSX == tileEX && tileSY == tileEY) break;