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