1 module d2d.core.quadtree; 2 3 import std.algorithm; 4 import std.conv; 5 import std.functional; 6 import std.range; 7 import std.string; 8 9 import gl3n.linalg; 10 11 import d2d.core.utils; 12 13 struct QuadTree(T, size_t maxObjects, size_t maxDepth, string mapper = "a") 14 { 15 @disable this(this); 16 17 rect bounds; 18 19 static if (maxDepth > 0) 20 { 21 alias SubTree = QuadTree!(T, maxObjects, maxDepth - 1, mapper); 22 /// 0 | 1 23 /// ---+--- 24 /// 2 | 3 25 SubTree*[4] nodes; 26 } 27 28 T[] children; 29 30 static rect map(T item) 31 { 32 return unaryFun!mapper(item); 33 } 34 35 void clear() 36 { 37 static if (maxDepth > 0) 38 if (nodes[0]!is null) 39 foreach (ref node; nodes) 40 { 41 node.clear(); 42 node = null; 43 } 44 45 children.length = 0; 46 } 47 48 static if (maxDepth > 0) 49 { 50 private void split() 51 { 52 const center = bounds.center; 53 54 nodes[0] = new SubTree(rect.dim(0, 0, center)); 55 nodes[1] = new SubTree(rect.dim(center.x, 0, center)); 56 nodes[2] = new SubTree(rect.dim(0, center.y, center)); 57 nodes[3] = new SubTree(rect.dim(center, center)); 58 } 59 60 bool isSplit() @property const @safe 61 { 62 return nodes[0]!is null; 63 } 64 65 /// Determines in which quadrant a rectangle would fall or -1 if it spans multiple. 66 static if (!is(T : rect)) 67 int getQuadrant(T item) 68 { 69 return getQuadrant(map(item)); 70 } 71 72 /// ditto 73 int getQuadrant(rect r) 74 { 75 int index = -1; 76 const center = bounds.center; 77 78 const aboveMidpoint = r.y < center.y && r.y + r.height < center.y; 79 const belowMidpoint = r.y > center.y && r.y + r.height > center.y; 80 const leftOfMidpoint = r.x < center.x && r.x + r.width < center.x; 81 const rightOfMidpoint = r.x > center.x && r.x + r.width > center.x; 82 83 if (aboveMidpoint) 84 { 85 if (leftOfMidpoint) 86 index = 0; 87 else if (rightOfMidpoint) 88 index = 1; 89 } 90 else if (belowMidpoint) 91 { 92 if (leftOfMidpoint) 93 index = 2; 94 else if (rightOfMidpoint) 95 index = 3; 96 } 97 98 return index; 99 } 100 } 101 102 void insert(T item) 103 { 104 static if (maxDepth > 0) 105 { 106 if (nodes[0]!is null) 107 { 108 auto index = getQuadrant(item); 109 if (index >= 0) 110 { 111 nodes[index].insert(item); 112 return; 113 } 114 } 115 } 116 117 children.assumeSafeAppend() ~= item; 118 119 static if (maxDepth > 0) 120 { 121 if (children.length > maxObjects) 122 { 123 if (nodes[0] is null) 124 split(); 125 126 size_t i; 127 while (i < children.length) 128 { 129 auto index = getQuadrant(children[i]); 130 if (index == -1) 131 i++; 132 else 133 { 134 auto other = children[i]; 135 children[i] = children[$ - 1]; 136 children.length--; 137 nodes[index].insert(other); 138 } 139 } 140 } 141 } 142 } 143 144 bool remove(T item) 145 { 146 if (item is T.init) 147 return false; 148 149 static if (maxDepth > 0) 150 { 151 if (nodes[0]!is null) 152 { 153 auto index = getQuadrant(item); 154 if (index >= 0) 155 { 156 auto ret = nodes[index].remove(item); 157 if (ret) 158 return true; 159 } 160 } 161 } 162 163 foreach (i, child; children) 164 if (child is item) 165 { 166 children[i] = children[$ - 1]; 167 children.length--; 168 return true; 169 } 170 171 return false; 172 } 173 174 T[] retrieve(rect r, bool exact = true) 175 { 176 static if (maxDepth > 0) 177 { 178 auto ret = appender!(T[]); 179 if (exact) 180 ret.put(children.filter!(a => map(a).intersects(r))); 181 else 182 ret.put(children); 183 184 if (nodes[0]!is null) 185 { 186 auto index = getQuadrant(r); 187 if (index != -1) 188 ret.put(nodes[index].retrieve(r, exact)); 189 else 190 foreach (ref node; nodes) 191 ret.put(node.retrieve(r, exact)); 192 } 193 return ret.data; 194 } 195 else 196 { 197 if (exact) 198 return children.filter!(a => map(a).intersects(r)).array; 199 else 200 return children; 201 } 202 } 203 204 string toString() const @safe 205 { 206 auto ret = appender!string; 207 ret.put("quad!"); 208 ret.put(maxDepth.to!string); 209 210 ret.put("(self: "); 211 ret.put(children.length.to!string); 212 static if (maxDepth > 0) 213 if (isSplit) 214 { 215 ret.put(",\n"); 216 ret.put(("NW: " ~ nodes[0].toString()).indent); 217 ret.put(",\n"); 218 ret.put(("NE: " ~ nodes[1].toString()).indent); 219 ret.put(",\n"); 220 ret.put(("SE: " ~ nodes[2].toString()).indent); 221 ret.put(",\n"); 222 ret.put(("SW: " ~ nodes[3].toString()).indent); 223 } 224 ret.put(")"); 225 226 return ret.data; 227 } 228 } 229 230 private string indent(string s, string indentation = "\t") @safe 231 { 232 return s.lineSplitter!(KeepTerminator.yes) 233 .map!(a => a.length ? indentation ~ a : a) 234 .join; 235 } 236 237 unittest 238 { 239 QuadTree!(rect, 2, 5) quadtree; 240 quadtree.bounds = rect.dim(800, 600); 241 242 assert(quadtree.retrieve(rect.dim(800, 600)).count == 0); 243 244 quadtree.insert(rect.dim(40, 40, 100, 100)); 245 246 assert(quadtree.retrieve(rect.dim(800, 600), false).count == 1); 247 assert(quadtree.retrieve(rect.dim(139, 40, 100, 100), false).count == 1); 248 assert(quadtree.retrieve(rect.dim(140, 40, 100, 100), false).count == 1); 249 assert(quadtree.retrieve(rect.dim(141, 40, 100, 100), false).count == 1); 250 assert(quadtree.retrieve(rect.dim(40, 139, 100, 100), false).count == 1); 251 assert(quadtree.retrieve(rect.dim(40, 140, 100, 100), false).count == 1); 252 assert(quadtree.retrieve(rect.dim(40, 141, 100, 100), false).count == 1); 253 254 assert(quadtree.retrieve(rect.dim(800, 600)).count == 1); 255 assert(quadtree.retrieve(rect.dim(139, 40, 100, 100)).count == 1); 256 assert(quadtree.retrieve(rect.dim(140, 40, 100, 100)).count == 1); 257 assert(quadtree.retrieve(rect.dim(141, 40, 100, 100)).count == 0); 258 assert(quadtree.retrieve(rect.dim(40, 139, 100, 100)).count == 1); 259 assert(quadtree.retrieve(rect.dim(40, 140, 100, 100)).count == 1); 260 assert(quadtree.retrieve(rect.dim(40, 141, 100, 100)).count == 0); 261 262 assert(!quadtree.isSplit); 263 264 quadtree.insert(rect.dim(30, 410, 20, 20)); 265 assert(!quadtree.isSplit); 266 assert(quadtree.retrieve(rect.dim(800, 600), false).count == 2); 267 assert(quadtree.retrieve(rect.dim(0, 0, 1, 1), false).count == 2); 268 assert(quadtree.retrieve(rect.dim(400, 0, 1, 1), false).count == 2); 269 270 quadtree.insert(rect.dim(410, 10, 20, 20)); 271 assert(quadtree.isSplit); 272 assert(quadtree.retrieve(rect.dim(800, 600), false).count == 3); 273 assert(quadtree.retrieve(rect.dim(1, 1, 1, 1), false).count == 1); 274 assert(quadtree.retrieve(rect.dim(401, 1, 1, 1), false).count == 1); 275 assert(quadtree.retrieve(rect.dim(1, 401, 1, 1), false).count == 1); 276 assert(quadtree.retrieve(rect.dim(401, 401, 1, 1), false).count == 0); 277 278 quadtree.insert(rect.dim(390, 390, 20, 20)); 279 assert(quadtree.retrieve(rect.dim(800, 600), false).count == 4); 280 assert(quadtree.retrieve(rect.dim(1, 1, 1, 1), false).count == 2); 281 assert(quadtree.retrieve(rect.dim(401, 1, 1, 1), false).count == 2); 282 assert(quadtree.retrieve(rect.dim(1, 401, 1, 1), false).count == 2); 283 assert(quadtree.retrieve(rect.dim(401, 401, 1, 1), false).count == 1); 284 }