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 }