right hand yellow
[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 u32 idx = node->start+i;
73 bh->system->expand_bound( bh->user, node->bbx, idx );
74 }
75 }
76
77 VG_STATIC void bh_subdivide( bh_tree *bh, u32 inode )
78 {
79 bh_node *node = &bh->nodes[ inode ];
80
81 if( node->count <= bh->max_per_leaf )
82 return;
83
84 v3f extent;
85 v3_sub( node->bbx[1], node->bbx[0], extent );
86
87 int axis = 0;
88 if( extent[1] > extent[0] ) axis = 1;
89 if( extent[2] > extent[axis] ) axis = 2;
90
91 float split = node->bbx[0][axis] + extent[axis]*0.5f;
92 float avg = 0.0;
93 for( u32 t=0; t<node->count; t++ )
94 {
95 u32 idx = node->start+t;
96 avg += bh->system->item_centroid( bh->user, idx, axis );
97 }
98 avg /= (float)node->count;
99 split = avg;
100
101
102 i32 i = node->start,
103 j = i + node->count-1;
104
105 while( i <= j ){
106 if( bh->system->item_centroid( bh->user, i, axis ) < split )
107 i ++;
108 else{
109 bh->system->item_swap( bh->user, i, j );
110 j --;
111 }
112 }
113
114 u32 left_count = i - node->start;
115 if( left_count == 0 || left_count == node->count ) return;
116
117 u32 il = bh->node_count ++,
118 ir = bh->node_count ++;
119
120 bh_node *lnode = &bh->nodes[il],
121 *rnode = &bh->nodes[ir];
122
123 lnode->start = node->start;
124 lnode->count = left_count;
125 rnode->start = i;
126 rnode->count = node->count - left_count;
127
128 node->il = il;
129 node->ir = ir;
130 node->count = 0;
131
132 bh_update_bounds( bh, il );
133 bh_update_bounds( bh, ir );
134 bh_subdivide( bh, il );
135 bh_subdivide( bh, ir );
136 }
137
138 VG_STATIC bh_tree *bh_create( void *lin_alloc, bh_system *system,
139 void *user, u32 item_count, u32 max_per_leaf )
140 {
141 assert( max_per_leaf > 0 );
142
143 u32 alloc_count = VG_MAX( 1, item_count );
144
145 u32 totsize = sizeof(bh_tree) + sizeof(bh_node)*(alloc_count*2-1);
146 bh_tree *bh = vg_linear_alloc( lin_alloc, vg_align8(totsize) );
147 bh->system = system;
148 bh->user = user;
149 bh->max_per_leaf = max_per_leaf;
150
151 bh_node *root = &bh->nodes[0];
152 bh->node_count = 1;
153
154 root->il = 0;
155 root->ir = 0;
156 root->count = item_count;
157 root->start = 0;
158
159 bh_update_bounds( bh, 0 );
160
161 if( item_count > 2 )
162 bh_subdivide( bh, 0 );
163
164 totsize = vg_align8(sizeof(bh_tree) + sizeof(bh_node) * bh->node_count);
165 bh = vg_linear_resize( lin_alloc, bh, totsize );
166
167 vg_success( "BVH done, size: %u/%u\n", bh->node_count, (alloc_count*2-1) );
168 return bh;
169 }
170
171 /*
172 * Draw items in this leaf node.
173 * *item_debug() must be set!
174 */
175 VG_STATIC void bh_debug_leaf( bh_tree *bh, bh_node *node )
176 {
177 vg_line_boxf( node->bbx, 0xff00ff00 );
178
179 if( bh->system->item_debug ){
180 for( u32 i=0; i<node->count; i++ ){
181 u32 idx = node->start+i;
182 bh->system->item_debug( bh->user, idx );
183 }
184 }
185 }
186
187 /*
188 * Trace the bh tree all the way down to the leaf nodes where pos is inside
189 */
190 VG_STATIC void bh_debug_trace( bh_tree *bh, u32 inode, v3f pos, u32 colour )
191 {
192 bh_node *node = &bh->nodes[ inode ];
193
194 if( (pos[0] >= node->bbx[0][0] && pos[0] <= node->bbx[1][0]) &&
195 (pos[2] >= node->bbx[0][2] && pos[2] <= node->bbx[1][2]) )
196 {
197 if( !node->count ){
198 vg_line_boxf( node->bbx, colour );
199
200 bh_debug_trace( bh, node->il, pos, colour );
201 bh_debug_trace( bh, node->ir, pos, colour );
202 }
203 else{
204 if( bh->system->item_debug )
205 bh_debug_leaf( bh, node );
206 }
207 }
208 }
209
210 VG_STATIC int bh_ray( bh_tree *bh, v3f co, v3f dir, ray_hit *hit )
211 {
212 if( bh->node_count < 2 )
213 return 0;
214
215 int count = 0;
216 u32 stack[100];
217 u32 depth = 2;
218
219 stack[0] = 0;
220 stack[1] = bh->nodes[0].il;
221 stack[2] = bh->nodes[0].ir;
222
223 v3f dir_inv;
224 dir_inv[0] = 1.0f/dir[0];
225 dir_inv[1] = 1.0f/dir[1];
226 dir_inv[2] = 1.0f/dir[2];
227
228 while(depth){
229 bh_node *inode = &bh->nodes[ stack[depth] ];
230 if( ray_aabb1( inode->bbx, co, dir_inv, hit->dist ) ){
231 if( inode->count ){
232 for( u32 i=0; i<inode->count; i++ ){
233 u32 idx = inode->start+i;
234
235 if( bh->system->cast_ray )
236 count += bh->system->cast_ray( bh->user, idx, co, dir, hit );
237 else
238 count ++;
239 }
240
241 depth --;
242 }
243 else{
244 if( depth+1 >= vg_list_size(stack) ){
245 vg_error( "Maximum stack reached!\n" );
246 return count;
247 }
248
249 stack[depth] = inode->il;
250 stack[depth+1] = inode->ir;
251 depth ++;
252 }
253 }
254 else{
255 depth --;
256 }
257 }
258
259 return count;
260 }
261
262 typedef struct bh_iter bh_iter;
263 struct bh_iter
264 {
265 struct
266 {
267 int id, depth;
268 }
269 stack[64];
270
271 int depth, i;
272 };
273
274 VG_STATIC void bh_iter_init( int root, bh_iter *it )
275 {
276 it->stack[0].id = root;
277 it->stack[0].depth = 0;
278 it->depth = 0;
279 it->i = 0;
280 }
281
282 VG_STATIC int bh_next( bh_tree *bh, bh_iter *it, boxf box, int *em )
283 {
284 while( it->depth >= 0 ){
285 bh_node *inode = &bh->nodes[ it->stack[it->depth].id ];
286
287 /* Only process overlapping nodes */
288 if( !box_overlap( inode->bbx, box ) ){
289 it->depth --;
290 continue;
291 }
292
293 if( inode->count ){
294 if( it->i < inode->count ){
295 *em = inode->start+it->i;
296 it->i ++;
297 return 1;
298 }
299 else{
300 it->depth --;
301 it->i = 0;
302 }
303 }
304 else{
305 if( it->depth+1 >= vg_list_size(it->stack) ){
306 vg_error( "Maximum stack reached!\n" );
307 return 0;
308 }
309
310 it->stack[it->depth ].id = inode->il;
311 it->stack[it->depth+1].id = inode->ir;
312 it->depth ++;
313 it->i = 0;
314 }
315 }
316
317 return 0;
318 }
319
320 VG_STATIC int bh_closest_point( bh_tree *bh, v3f pos,
321 v3f closest, float max_dist )
322 {
323 if( bh->node_count < 2 )
324 return -1;
325
326 max_dist = max_dist*max_dist;
327
328 int queue[ 128 ],
329 depth = 0,
330 best_item = -1;
331
332 queue[0] = 0;
333
334 while( depth >= 0 ){
335 bh_node *inode = &bh->nodes[ queue[depth] ];
336
337 v3f p1;
338 closest_point_aabb( pos, inode->bbx, p1 );
339
340 /* branch into node if its closer than current best */
341 float node_dist = v3_dist2( pos, p1 );
342 if( node_dist < max_dist ){
343 if( inode->count ){
344 for( int i=0; i<inode->count; i++ ){
345 v3f p2;
346 bh->system->item_closest( bh->user, inode->start+i, pos, p2 );
347
348 float item_dist = v3_dist2( pos, p2 );
349 if( item_dist < max_dist ){
350 max_dist = item_dist;
351 v3_copy( p2, closest );
352 best_item = inode->start+i;
353 }
354 }
355
356 depth --;
357 }
358 else{
359 queue[depth] = inode->il;
360 queue[depth+1] = inode->ir;
361
362 depth ++;
363 }
364 }
365 else
366 depth --;
367 }
368
369 return best_item;
370 }
371
372 #endif /* BVH_H */