naev 0.11.5
quadtree.c
1/*
2 * This code was initially authored by the Stackoverflow user dragon-energy and posted under following page:
3 * https://stackoverflow.com/questions/41946007/efficient-and-well-explained-implementation-of-a-quadtree-for-2d-collision-det
4 *
5 * As for the license, the author has kindly noted:
6 *
7 * "Oh and feel free to use this code I post however you want, even for commercial projects.
8 * I would really love it if people let me know if they find it useful, but do as you wish."
9 *
10 * And generally all Stackoverflow-posted code is by default licensed with CC BY-SA 4.0:
11 * https://creativecommons.org/licenses/by-sa/4.0/
12 */
13#include "quadtree.h"
14#include <stdlib.h>
15#include <string.h>
16
17enum
18{
19 // ----------------------------------------------------------------------------------------
20 // Element node fields:
21 // ----------------------------------------------------------------------------------------
22 enode_num = 2,
23
24 // Points to the next element in the leaf node. A value of -1
25 // indicates the end of the list.
26 enode_idx_next = 0,
27
28 // Stores the element index.
29 enode_idx_elt = 1,
30
31 // ----------------------------------------------------------------------------------------
32 // Element fields:
33 // ----------------------------------------------------------------------------------------
34 elt_num = 5,
35
36 // Stores the rectangle encompassing the element.
37 elt_idx_lft = 0, elt_idx_top = 1, elt_idx_rgt = 2, elt_idx_btm = 3,
38
39 // Stores the ID of the element.
40 elt_idx_id = 4,
41
42 // ----------------------------------------------------------------------------------------
43 // Node fields:
44 // ----------------------------------------------------------------------------------------
45 node_num = 2,
46
47 // Points to the first child if this node is a branch or the first element
48 // if this node is a leaf.
49 node_idx_fc = 0,
50
51 // Stores the number of elements in the node or -1 if it is not a leaf.
52 node_idx_num = 1,
53
54 // ----------------------------------------------------------------------------------------
55 // Node data fields:
56 // ----------------------------------------------------------------------------------------
57 nd_num = 6,
58
59 // Stores the extents of the node using a centered rectangle and half-size.
60 nd_idx_mx = 0, nd_idx_my = 1, nd_idx_sx = 2, nd_idx_sy = 3,
61
62 // Stores the index of the node.
63 nd_idx_index = 4,
64
65 // Stores the depth of the node.
66 nd_idx_depth = 5,
67};
68
69static void node_insert(Quadtree* qt, int index, int depth, int mx, int my, int sx, int sy, int element);
70
71static int intersect( int l1, int t1, int r1, int b1, int l2, int t2, int r2, int b2 )
72{
73 return l2 <= r1 && r2 >= l1 && t2 <= b1 && b2 >= t1;
74}
75
76static void leaf_insert( Quadtree* qt, int node, int depth, int mx, int my, int sx, int sy, int element )
77{
78 // Insert the element node to the leaf.
79 const int nd_fc = il_get(&qt->nodes, node, node_idx_fc);
80 il_set(&qt->nodes, node, node_idx_fc, il_insert(&qt->enodes));
81 il_set(&qt->enodes, il_get(&qt->nodes, node, node_idx_fc), enode_idx_next, nd_fc);
82 il_set(&qt->enodes, il_get(&qt->nodes, node, node_idx_fc), enode_idx_elt, element);
83
84 // If the leaf is full, split it.
85 if (il_get(&qt->nodes, node, node_idx_num) == qt->max_elements && depth < qt->max_depth) {
86 int fc = 0;
87 IntList elts = {0};
88 il_create(&elts, 1);
89
90 // Transfer elements from the leaf node to a list of elements.
91 while (il_get(&qt->nodes, node, node_idx_fc) != -1) {
92 const int index = il_get(&qt->nodes, node, node_idx_fc);
93 const int next_index = il_get(&qt->enodes, index, enode_idx_next);
94 const int elt = il_get(&qt->enodes, index, enode_idx_elt);
95
96 // Pop off the element node from the leaf and remove it from the qt.
97 il_set(&qt->nodes, node, node_idx_fc, next_index);
98 il_erase(&qt->enodes, index);
99
100 // Insert element to the list.
101 il_set(&elts, il_push_back(&elts), 0, elt);
102 }
103
104 // Start by allocating 4 child nodes.
105 fc = il_insert(&qt->nodes);
106 il_insert(&qt->nodes);
107 il_insert(&qt->nodes);
108 il_insert(&qt->nodes);
109 il_set(&qt->nodes, node, node_idx_fc, fc);
110
111 // Initialize the new child nodes.
112 for (int j=0; j < 4; ++j) {
113 il_set(&qt->nodes, fc+j, node_idx_fc, -1);
114 il_set(&qt->nodes, fc+j, node_idx_num, 0);
115 }
116
117 // Transfer the elements in the former leaf node to its new children.
118 il_set(&qt->nodes, node, node_idx_num, -1);
119 for (int j=0; j < il_size(&elts); ++j)
120 node_insert(qt, node, depth, mx, my, sx, sy, il_get(&elts, j, 0));
121 il_destroy(&elts);
122 }
123 else {
124 // Increment the leaf element count.
125 il_set(&qt->nodes, node, node_idx_num, il_get(&qt->nodes, node, node_idx_num) + 1);
126 }
127}
128
129static void push_node( IntList* nodes, int nd_index, int nd_depth, int nd_mx, int nd_my, int nd_sx, int nd_sy )
130{
131 const int back_idx = il_push_back(nodes);
132 il_set(nodes, back_idx, nd_idx_mx, nd_mx);
133 il_set(nodes, back_idx, nd_idx_my, nd_my);
134 il_set(nodes, back_idx, nd_idx_sx, nd_sx);
135 il_set(nodes, back_idx, nd_idx_sy, nd_sy);
136 il_set(nodes, back_idx, nd_idx_index, nd_index);
137 il_set(nodes, back_idx, nd_idx_depth, nd_depth);
138}
139
140static void find_leaves( IntList* out, const Quadtree* qt, int node, int depth,
141 int mx, int my, int sx, int sy,
142 int lft, int top, int rgt, int btm )
143{
144 IntList to_process = {0};
145 il_create( &to_process, nd_num );
146 push_node( &to_process, node, depth, mx, my, sx, sy );
147
148 while (il_size(&to_process) > 0) {
149 const int back_idx = il_size(&to_process) - 1;
150 const int nd_mx = il_get(&to_process, back_idx, nd_idx_mx);
151 const int nd_my = il_get(&to_process, back_idx, nd_idx_my);
152 const int nd_sx = il_get(&to_process, back_idx, nd_idx_sx);
153 const int nd_sy = il_get(&to_process, back_idx, nd_idx_sy);
154 const int nd_index = il_get(&to_process, back_idx, nd_idx_index);
155 const int nd_depth = il_get(&to_process, back_idx, nd_idx_depth);
156 il_pop_back(&to_process);
157
158 // If this node is a leaf, insert it to the list.
159 if (il_get(&qt->nodes, nd_index, node_idx_num) != -1)
160 push_node(out, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
161 else {
162 // Otherwise push the children that intersect the rectangle.
163 const int fc = il_get(&qt->nodes, nd_index, node_idx_fc);
164 const int hx = nd_sx >> 1, hy = nd_sy >> 1;
165 const int l = nd_mx-hx, t = nd_my-hy, r = nd_mx+hx, b = nd_my+hy;
166
167 if (top <= nd_my) {
168 if (lft <= nd_mx)
169 push_node(&to_process, fc+0, nd_depth+1, l,t,hx,hy);
170 if (rgt > nd_mx)
171 push_node(&to_process, fc+1, nd_depth+1, r,t,hx,hy);
172 }
173 if (btm > nd_my) {
174 if (lft <= nd_mx)
175 push_node(&to_process, fc+2, nd_depth+1, l,b,hx,hy);
176 if (rgt > nd_mx)
177 push_node(&to_process, fc+3, nd_depth+1, r,b,hx,hy);
178 }
179 }
180 }
181 il_destroy(&to_process);
182}
183
184static void node_insert( Quadtree* qt, int index, int depth, int mx, int my, int sx, int sy, int element )
185{
186 // Find the leaves and insert the element to all the leaves found.
187 IntList leaves = {0};
188
189 const int lft = il_get(&qt->elts, element, elt_idx_lft);
190 const int top = il_get(&qt->elts, element, elt_idx_top);
191 const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
192 const int btm = il_get(&qt->elts, element, elt_idx_btm);
193
194 il_create(&leaves, nd_num);
195 find_leaves(&leaves, qt, index, depth, mx, my, sx, sy, lft, top, rgt, btm);
196 for (int j=0; j < il_size(&leaves); ++j) {
197 const int nd_mx = il_get(&leaves, j, nd_idx_mx);
198 const int nd_my = il_get(&leaves, j, nd_idx_my);
199 const int nd_sx = il_get(&leaves, j, nd_idx_sx);
200 const int nd_sy = il_get(&leaves, j, nd_idx_sy);
201 const int nd_index = il_get(&leaves, j, nd_idx_index);
202 const int nd_depth = il_get(&leaves, j, nd_idx_depth);
203 leaf_insert(qt, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy, element);
204 }
205 il_destroy(&leaves);
206}
207
208void qt_create( Quadtree* qt, int x1, int y1, int x2, int y2, int max_elements, int max_depth )
209{
210 qt->max_elements = max_elements;
211 qt->max_depth = max_depth;
212 qt->temp = NULL;
213 qt->temp_size = 0;
214 il_create(&qt->nodes, node_num);
215 il_create(&qt->elts, elt_num);
216 il_create(&qt->enodes, enode_num);
217
218 // Insert the root node to the qt.
219 il_insert(&qt->nodes);
220 il_set(&qt->nodes, 0, node_idx_fc, -1);
221 il_set(&qt->nodes, 0, node_idx_num, 0);
222
223 int half_width = (x2-x1) >> 1;
224 int half_height = (y2-y1) >> 1;
225 qt->root_sx = half_width;
226 qt->root_sy = half_height;
227
228 // Center
229 qt->root_mx = x1 + half_width;
230 qt->root_my = y1 + half_height;
231}
232
233void qt_clear( Quadtree* qt )
234{
235 il_clear( &qt->nodes );
236 il_clear( &qt->elts );
237 il_clear( &qt->enodes );
238
239 // Insert the root node to the qt.
240 il_insert(&qt->nodes);
241 il_set(&qt->nodes, 0, node_idx_fc, -1);
242 il_set(&qt->nodes, 0, node_idx_num, 0);
243}
244
245void qt_destroy( Quadtree* qt )
246{
247 il_destroy(&qt->nodes);
248 il_destroy(&qt->elts);
249 il_destroy(&qt->enodes);
250 free(qt->temp);
251}
252
253int qt_insert (Quadtree* qt, int id, int x1, int y1, int x2, int y2 )
254{
255 // Insert a new element.
256 const int new_element = il_insert(&qt->elts);
257
258 // Set the fields of the new element.
259 il_set(&qt->elts, new_element, elt_idx_lft, x1);
260 il_set(&qt->elts, new_element, elt_idx_top, y1);
261 il_set(&qt->elts, new_element, elt_idx_rgt, x2);
262 il_set(&qt->elts, new_element, elt_idx_btm, y2);
263 il_set(&qt->elts, new_element, elt_idx_id, id);
264
265 // Insert the element to the appropriate leaf node(s).
266 node_insert(qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, new_element);
267 return new_element;
268}
269
270void qt_remove( Quadtree* qt, int element )
271{
272 // Find the leaves.
273 IntList leaves = {0};
274
275 const int lft = il_get(&qt->elts, element, elt_idx_lft);
276 const int top = il_get(&qt->elts, element, elt_idx_top);
277 const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
278 const int btm = il_get(&qt->elts, element, elt_idx_btm);
279
280 il_create(&leaves, nd_num);
281 find_leaves(&leaves, qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, lft, top, rgt, btm);
282
283 // For each leaf node, remove the element node.
284 for (int j=0; j < il_size(&leaves); ++j) {
285 const int nd_index = il_get(&leaves, j, nd_idx_index);
286
287 // Walk the list until we find the element node.
288 int node_index = il_get(&qt->nodes, nd_index, node_idx_fc);
289 int prev_index = -1;
290 while (node_index != -1 && il_get(&qt->enodes, node_index, enode_idx_elt) != element) {
291 prev_index = node_index;
292 node_index = il_get(&qt->enodes, node_index, enode_idx_next);
293 }
294
295 if (node_index != -1) {
296 // Remove the element node.
297 const int next_index = il_get(&qt->enodes, node_index, enode_idx_next);
298 if (prev_index == -1)
299 il_set(&qt->nodes, nd_index, node_idx_fc, next_index);
300 else
301 il_set(&qt->enodes, prev_index, enode_idx_next, next_index);
302 il_erase(&qt->enodes, node_index);
303
304 // Decrement the leaf element count.
305 il_set(&qt->nodes, nd_index, node_idx_num, il_get(&qt->nodes, nd_index, node_idx_num)-1);
306 }
307 }
308 il_destroy(&leaves);
309
310 // Remove the element.
311 il_erase(&qt->elts, element);
312}
313
314void qt_query( Quadtree* qt, IntList* out, int qlft, int qtop, int qrgt, int qbtm )
315{
316 // Find the leaves that intersect the specified query rectangle.
317 IntList leaves = {0};
318 const int elt_cap = il_size(&qt->elts);
319
320 if (qt->temp_size < elt_cap) {
321 qt->temp_size = elt_cap;
322 qt->temp = realloc(qt->temp, qt->temp_size * sizeof(*qt->temp));
323 memset(qt->temp, 0, qt->temp_size * sizeof(*qt->temp));
324 }
325
326 // For each leaf node, look for elements that intersect.
327 il_create(&leaves, nd_num);
328 find_leaves(&leaves, qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, qlft, qtop, qrgt, qbtm);
329
330 il_clear(out);
331 for (int j=0; j < il_size(&leaves); ++j) {
332 const int nd_index = il_get(&leaves, j, nd_idx_index);
333
334 // Walk the list and add elements that intersect.
335 int elt_node_index = il_get(&qt->nodes, nd_index, node_idx_fc);
336 while (elt_node_index != -1) {
337 const int element = il_get(&qt->enodes, elt_node_index, enode_idx_elt);
338 const int lft = il_get(&qt->elts, element, elt_idx_lft);
339 const int top = il_get(&qt->elts, element, elt_idx_top);
340 const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
341 const int btm = il_get(&qt->elts, element, elt_idx_btm);
342 if (!qt->temp[element] && intersect(qlft,qtop,qrgt,qbtm, lft,top,rgt,btm)) {
343 il_set(out, il_push_back(out), 0, element);
344 qt->temp[element] = 1;
345 }
346 elt_node_index = il_get(&qt->enodes, elt_node_index, enode_idx_next);
347 }
348 }
349 il_destroy(&leaves);
350
351 /* Unmark the elements that were inserted, and convert to IDs. */
352 for (int j=0; j < il_size(out); ++j) {
353 const int element = il_get(out, j, 0);
354 const int id = il_get(&qt->elts, element, elt_idx_id);
355 qt->temp[element] = 0;
356 il_set(out, j, 0, id);
357 }
358}
359
360void qt_cleanup( Quadtree* qt )
361{
362 IntList to_process = {0};
363 il_create(&to_process, 1);
364
365 // Only process the root if it's not a leaf.
366 if (il_get(&qt->nodes, 0, node_idx_num) == -1) {
367 // Push the root index to the stack.
368 il_set(&to_process, il_push_back(&to_process), 0, 0);
369 }
370
371 while (il_size(&to_process) > 0) {
372 // Pop a node from the stack.
373 const int node = il_get(&to_process, il_size(&to_process)-1, 0);
374 const int fc = il_get(&qt->nodes, node, node_idx_fc);
375 int num_empty_leaves = 0;
376 il_pop_back(&to_process);
377
378 // Loop through the children.
379 for (int j=0; j < 4; ++j) {
380 const int child = fc + j;
381
382 // Increment empty leaf count if the child is an empty
383 // leaf. Otherwise if the child is a branch, add it to
384 // the stack to be processed in the next iteration.
385 if (il_get(&qt->nodes, child, node_idx_num) == 0)
386 ++num_empty_leaves;
387 else if (il_get(&qt->nodes, child, node_idx_num) == -1)
388 {
389 // Push the child index to the stack.
390 il_set(&to_process, il_push_back(&to_process), 0, child);
391 }
392 }
393
394 // If all the children were empty leaves, remove them and
395 // make this node the new empty leaf.
396 if (num_empty_leaves == 4) {
397 // Remove all 4 children in reverse order so that they
398 // can be reclaimed on subsequent insertions in proper
399 // order.
400 il_erase(&qt->nodes, fc + 3);
401 il_erase(&qt->nodes, fc + 2);
402 il_erase(&qt->nodes, fc + 1);
403 il_erase(&qt->nodes, fc + 0);
404
405 // Make this node the new empty leaf.
406 il_set(&qt->nodes, node, node_idx_fc, -1);
407 il_set(&qt->nodes, node, node_idx_num, 0);
408 }
409 }
410 il_destroy(&to_process);
411}
412
413void qt_traverse( Quadtree* qt, void* user_data, QtNodeFunc* branch, QtNodeFunc* leaf )
414{
415 IntList to_process = {0};
416 il_create(&to_process, nd_num);
417 push_node(&to_process, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy);
418
419 while (il_size(&to_process) > 0) {
420 const int back_idx = il_size(&to_process) - 1;
421 const int nd_mx = il_get(&to_process, back_idx, nd_idx_mx);
422 const int nd_my = il_get(&to_process, back_idx, nd_idx_my);
423 const int nd_sx = il_get(&to_process, back_idx, nd_idx_sx);
424 const int nd_sy = il_get(&to_process, back_idx, nd_idx_sy);
425 const int nd_index = il_get(&to_process, back_idx, nd_idx_index);
426 const int nd_depth = il_get(&to_process, back_idx, nd_idx_depth);
427 const int fc = il_get(&qt->nodes, nd_index, node_idx_fc);
428 il_pop_back(&to_process);
429
430 if (il_get(&qt->nodes, nd_index, node_idx_num) == -1) {
431 // Push the children of the branch to the stack.
432 const int hx = nd_sx >> 1, hy = nd_sy >> 1;
433 const int l = nd_mx-hx, t = nd_my-hy, r = nd_mx+hx, b = nd_my+hy;
434 push_node(&to_process, fc+0, nd_depth+1, l,t, hx,hy);
435 push_node(&to_process, fc+1, nd_depth+1, r,t, hx,hy);
436 push_node(&to_process, fc+2, nd_depth+1, l,b, hx,hy);
437 push_node(&to_process, fc+3, nd_depth+1, r,b, hx,hy);
438 if (branch)
439 branch(qt, user_data, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
440 }
441 else if (leaf)
442 leaf(qt, user_data, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
443 }
444 il_destroy(&to_process);
445}