52ce24ab75e39175923ab95a00e508882349afe8
[carveJwlIkooP6JGAAIwe30JlM.git] / bvh.h
1 /*
2 * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 */
4
5 #ifndef BVH_H
6 #define BVH_H
7 #include "common.h"
8
9 /*
10 * Usage:
11 *
12 * create a bh_system with functions filled out for expand, centroid, and swap.
13 * optionally include item_debug and cast_ray functions if needed, otherwise,
14 * set them to null
15 *
16 * create a bh_tree struct with:
17 * user: a pointer back the base of the data you are ordering
18 * system: the system we created above which will deal with the data
19 *
20 * call bh_create( bh_tree *bh, u32 item_count )
21 * VG_STATIC int bh_ray( bh_tree *bh, u32 inode, v3f co, v3f dir, ray_hit *hit )
22 * VG_STATIC int bh_select( bh_tree *bh, boxf box, u32 *buffer, int len )
23 */
24
25 typedef struct bh_node bh_node;
26 typedef struct bh_tree bh_tree;
27 typedef struct bh_system bh_system;
28
29 struct bh_tree
30 {
31 u32 node_count;
32
33 bh_system *system;
34 void *user;
35 u32 max_per_leaf;
36
37 struct bh_node
38 {
39 boxf bbx;
40
41 /* if il is 0, this is a leaf */
42 int il, count;
43 union{ int ir, start; };
44 }
45 nodes[];
46 };
47
48 struct bh_system
49 {
50 void (*expand_bound)( void *user, boxf bound, u32 item_index );
51 float (*item_centroid)( void *user, u32 item_index, int axis );
52 void (*item_closest)( void *user, u32 item_index, v3f point, v3f closest );
53 void (*item_swap)( void *user, u32 ia, u32 ib );
54
55 /*
56 * Optional:
57 * item_debug - draw this item quickly usually with lines
58 * cast_ray - shoot a ray against the object, if this is not set,
59 * raycasts will simply return the hit on the bvh node
60 */
61
62 void (*item_debug)( void *user, u32 item_index );
63 int (*cast_ray)( void *user, u32 index, v3f co, v3f dir, ray_hit *hit );
64 };
65
66 VG_STATIC void bh_update_bounds( bh_tree *bh, u32 inode )
67 {
68 bh_node *node = &bh->nodes[ inode ];
69
70 box_init_inf( node->bbx );
71 for( u32 i=0; i<node->count; i++ )
72 {
73 u32 idx = node->start+i;
74 bh->system->expand_bound( bh->user, node->bbx, idx );
75 }
76 }
77
78 VG_STATIC void bh_subdivide( bh_tree *bh, u32 inode )
79 {
80 bh_node *node = &bh->nodes[ inode ];
81
82 if( node->count <= bh->max_per_leaf )
83 return;
84
85 v3f extent;
86 v3_sub( node->bbx[1], node->bbx[0], extent );
87
88 int axis = 0;
89 if( extent[1] > extent[0] ) axis = 1;
90 if( extent[2] > extent[axis] ) axis = 2;
91
92 float split = node->bbx[0][axis] + extent[axis]*0.5f;
93 float avg = 0.0;
94 for( u32 t=0; t<node->count; t++ )
95 {
96 u32 idx = node->start+t;
97 avg += bh->system->item_centroid( bh->user, idx, axis );
98 }
99 avg /= (float)node->count;
100 split = avg;
101
102
103 i32 i = node->start,
104 j = i + node->count-1;
105
106 while( i <= j )
107 {
108 if( bh->system->item_centroid( bh->user, i, axis ) < split )
109 i ++;
110 else
111 {
112 bh->system->item_swap( bh->user, i, j );
113 j --;
114 }
115 }
116
117 u32 left_count = i - node->start;
118 if( left_count == 0 || left_count == node->count ) return;
119
120 u32 il = bh->node_count ++,
121 ir = bh->node_count ++;
122
123 bh_node *lnode = &bh->nodes[il],
124 *rnode = &bh->nodes[ir];
125
126 lnode->start = node->start;
127 lnode->count = left_count;
128 rnode->start = i;
129 rnode->count = node->count - left_count;
130
131 node->il = il;
132 node->ir = ir;
133 node->count = 0;
134
135 bh_update_bounds( bh, il );
136 bh_update_bounds( bh, ir );
137 bh_subdivide( bh, il );
138 bh_subdivide( bh, ir );
139 }
140
141 VG_STATIC bh_tree *bh_create( void *lin_alloc, bh_system *system,
142 void *user, u32 item_count, u32 max_per_leaf )
143 {
144 assert( max_per_leaf > 0 );
145
146 if( item_count == 0 )
147 {
148 bh_tree *bh = vg_linear_alloc( lin_alloc, sizeof(bh_tree) );
149 bh->node_count = 0;
150 bh->system = system;
151 bh->user = user;
152 return bh;
153 }
154
155 u32 totsize = sizeof(bh_tree) + sizeof(bh_node)*(item_count*2-1);
156 bh_tree *bh = vg_linear_alloc( lin_alloc, vg_align8(totsize) );
157 bh->system = system;
158 bh->user = user;
159 bh->max_per_leaf = max_per_leaf;
160
161 bh_node *root = &bh->nodes[0];
162 bh->node_count = 1;
163
164 root->il = 0;
165 root->ir = 0;
166 root->count = item_count;
167 root->start = 0;
168
169 bh_update_bounds( bh, 0 );
170 bh_subdivide( bh, 0 );
171
172 totsize = sizeof(bh_tree) + sizeof(bh_node) * bh->node_count;
173 bh = vg_linear_resize( lin_alloc, bh, totsize );
174
175 vg_success( "BVH done, size: %u/%u\n", bh->node_count, (item_count*2-1) );
176 return bh;
177 }
178
179 /*
180 * Draw items in this leaf node.
181 * *item_debug() must be set!
182 */
183 VG_STATIC void bh_debug_leaf( bh_tree *bh, bh_node *node )
184 {
185 vg_line_boxf( node->bbx, 0xff00ff00 );
186
187 if( bh->system->item_debug )
188 {
189 for( u32 i=0; i<node->count; i++ )
190 {
191 u32 idx = node->start+i;
192 bh->system->item_debug( bh->user, idx );
193 }
194 }
195 }
196
197 /*
198 * Trace the bh tree all the way down to the leaf nodes where pos is inside
199 */
200 VG_STATIC void bh_debug_trace( bh_tree *bh, u32 inode, v3f pos, u32 colour )
201 {
202 bh_node *node = &bh->nodes[ inode ];
203
204 if( (pos[0] >= node->bbx[0][0] && pos[0] <= node->bbx[1][0]) &&
205 (pos[2] >= node->bbx[0][2] && pos[2] <= node->bbx[1][2]) )
206 {
207 if( !node->count )
208 {
209 vg_line_boxf( node->bbx, colour );
210
211 bh_debug_trace( bh, node->il, pos, colour );
212 bh_debug_trace( bh, node->ir, pos, colour );
213 }
214 else
215 {
216 if( bh->system->item_debug )
217 bh_debug_leaf( bh, node );
218 }
219 }
220 }
221
222 VG_STATIC int bh_ray( bh_tree *bh, v3f co, v3f dir, ray_hit *hit )
223 {
224 if( bh->node_count < 2 )
225 return 0;
226
227 int count = 0;
228 u32 stack[100];
229 u32 depth = 2;
230
231 stack[0] = 0;
232 stack[1] = bh->nodes[0].il;
233 stack[2] = bh->nodes[0].ir;
234
235 v3f dir_inv;
236 dir_inv[0] = 1.0f/dir[0];
237 dir_inv[1] = 1.0f/dir[1];
238 dir_inv[2] = 1.0f/dir[2];
239
240 while(depth)
241 {
242 bh_node *inode = &bh->nodes[ stack[depth] ];
243 if( ray_aabb1( inode->bbx, co, dir_inv, hit->dist ) )
244 {
245 if( inode->count )
246 {
247 for( u32 i=0; i<inode->count; i++ )
248 {
249 u32 idx = inode->start+i;
250
251 if( bh->system->cast_ray )
252 count += bh->system->cast_ray( bh->user, idx, co, dir, hit );
253 else
254 count ++;
255 }
256
257 depth --;
258 }
259 else
260 {
261 if( depth+1 >= vg_list_size(stack) )
262 {
263 vg_error( "Maximum stack reached!\n" );
264 return count;
265 }
266
267 stack[depth] = inode->il;
268 stack[depth+1] = inode->ir;
269 depth ++;
270 }
271 }
272 else
273 {
274 depth --;
275 }
276 }
277
278 return count;
279 }
280
281 typedef struct bh_iter bh_iter;
282 struct bh_iter
283 {
284 struct
285 {
286 int id, depth;
287 }
288 stack[64];
289
290 int depth, i;
291 };
292
293 VG_STATIC void bh_iter_init( int root, bh_iter *it )
294 {
295 it->stack[0].id = root;
296 it->stack[0].depth = 0;
297 it->depth = 0;
298 it->i = 0;
299 }
300
301 VG_STATIC int bh_next( bh_tree *bh, bh_iter *it, boxf box, int *em )
302 {
303 while( it->depth >= 0 )
304 {
305 bh_node *inode = &bh->nodes[ it->stack[it->depth].id ];
306
307 /* Only process overlapping nodes */
308 if( !box_overlap( inode->bbx, box ) )
309 {
310 it->depth --;
311 continue;
312 }
313
314 if( inode->count )
315 {
316 if( it->i < inode->count )
317 {
318 *em = inode->start+it->i;
319 it->i ++;
320 return 1;
321 }
322 else
323 {
324 it->depth --;
325 it->i = 0;
326 }
327 }
328 else
329 {
330 if( it->depth+1 >= vg_list_size(it->stack) )
331 {
332 vg_error( "Maximum stack reached!\n" );
333 return 0;
334 }
335
336 it->stack[it->depth ].id = inode->il;
337 it->stack[it->depth+1].id = inode->ir;
338 it->depth ++;
339 it->i = 0;
340 }
341 }
342
343 return 0;
344 }
345
346 VG_STATIC int bh_closest_point( bh_tree *bh, v3f pos,
347 v3f closest, float max_dist )
348 {
349 if( bh->node_count < 2 )
350 return -1;
351
352 max_dist = max_dist*max_dist;
353
354 int queue[ 128 ],
355 depth = 0,
356 best_item = -1;
357
358 queue[0] = 0;
359
360 while( depth >= 0 )
361 {
362 bh_node *inode = &bh->nodes[ queue[depth] ];
363
364 v3f p1;
365 closest_point_aabb( pos, inode->bbx, p1 );
366
367 /* branch into node if its closer than current best */
368 float node_dist = v3_dist2( pos, p1 );
369 if( node_dist < max_dist )
370 {
371 if( inode->count )
372 {
373 for( int i=0; i<inode->count; i++ )
374 {
375 v3f p2;
376 bh->system->item_closest( bh->user, inode->start+i, pos, p2 );
377
378 float item_dist = v3_dist2( pos, p2 );
379 if( item_dist < max_dist )
380 {
381 max_dist = item_dist;
382 v3_copy( p2, closest );
383 best_item = inode->start+i;
384 }
385 }
386
387 depth --;
388 }
389 else
390 {
391 queue[depth] = inode->il;
392 queue[depth+1] = inode->ir;
393
394 depth ++;
395 }
396 }
397 else
398 depth --;
399 }
400
401 return best_item;
402 }
403
404 #endif /* BVH_H */