Pico Headers
Loading...
Searching...
No Matches
pico_bvh.h
Go to the documentation of this file.
1
69#ifndef PICO_BVH_H
70#define PICO_BVH_H
71
72#include <stdbool.h>
73#include <stdint.h>
74
75// --- User Data Type Override -------------------------------------------------
76
77#ifndef PICO_BVH_UDATA_TYPE
78 #define PICO_BVH_UDATA_TYPE uint64_t
79#endif
80
82
83// --- Math Primitives ---------------------------------------------------------
84
85typedef struct
86{
87 float x;
88 float y;
90
91typedef struct
92{
96
97// --- Public Types ------------------------------------------------------------
98
99#define BVH_NULL_ID (-1)
100
108typedef bool (*bvh_query_cb)(int leaf_id, bvh_udata_t udata, void* ctx);
109
118typedef void (*bvh_walk_cb)(bvh_aabb_t aabb, int depth, bool is_leaf,
119 bvh_udata_t udata, void* ctx);
120
124typedef struct bvh_t bvh_t;
125
126// --- Helpers -----------------------------------------------------------------
127
136bvh_aabb_t bvh_make_aabb(float x, float y, float w, float h);
137
138// --- Lifecycle ---------------------------------------------------------------
139
145
150void bvh_destroy(bvh_t* tree);
151
152// --- Modification ------------------------------------------------------------
153
165int bvh_insert(bvh_t* tree, bvh_aabb_t aabb, float padding, bvh_udata_t udata);
166
172void bvh_remove(bvh_t* tree, int leaf_id);
173
183bool bvh_move(bvh_t* tree, int leaf_id, bvh_aabb_t new_aabb, float padding);
184
185// --- Queries -----------------------------------------------------------------
186
194void bvh_query_aabb(const bvh_t* tree, bvh_aabb_t query,
195 bvh_query_cb cb, void* ctx);
207void bvh_query_ray(const bvh_t* tree,
208 bvh_vec2_t origin, bvh_vec2_t dir, float t_max,
209 bvh_query_cb cb, void* ctx);
210
211// --- Accessors ---------------------------------------------------------------
212
219bvh_udata_t bvh_get_udata(const bvh_t* tree, int leaf_id);
220
227bvh_aabb_t bvh_get_padded_aabb(const bvh_t* tree, int leaf_id);
228
234int bvh_get_leaf_count(const bvh_t* tree);
235
242void bvh_walk(const bvh_t* tree, bvh_walk_cb cb, void* ctx);
243
249float bvh_get_cost(const bvh_t* tree);
250
251#endif // PICO_BVH_H
252
253#ifdef PICO_BVH_IMPLEMENTATION
254
255/*
256 Design notes
257 ------------
258 The tree is a pool-allocated binary tree stored in a flat array.
259 Every node keeps:
260 • A "padded" AABB (tight AABB padded by a user-supplied padding).
261 • child[0], child[1] - BVH_NULL_ID for leaves.
262 • parent - BVH_NULL_ID for the root.
263 • height - 0 for leaves, max(child heights)+1 otherwise.
264 • udata - only meaningful for leaves.
265
266 Insertion (O(log n) expected)
267 ------------------------------
268 We pick the best sibling for a new leaf using the surface-area heuristic:
269 the sibling that minimises the total induced cost increase walking back to
270 the root. This is the exact O(log n) algorithm described by:
271 Bittner et al. "Fast, Effective BVH Updates for Animated Scenes" (2015)
272 (also used by Box2D's b2DynamicTree).
273
274 After every insertion / removal we walk back to the root and:
275 1. Refit the ancestor AABBs.
276 2. Apply one SAH rotation at each ancestor to reduce surface-area cost.
277
278 Rotations (SAH balance)
279 ------------------------
280 At each internal node we consider swapping one of its grandchildren with
281 the other child. If any swap reduces the node's induced surface area we
282 apply it. This keeps the tree height near O(log n) without a full rebuild.
283*/
284
285#include <float.h>
286#include <math.h>
287
288#ifdef NDEBUG
289 #define PICO_BVH_ASSERT(expr) ((void)0)
290#else
291 #ifndef PICO_BVH_ASSERT
292 #include <assert.h>
293 #define PICO_BVH_ASSERT(expr) (assert(expr))
294 #endif
295#endif
296
297#if !defined(PICO_BVH_CALLOC) || \
298 !defined(PICO_BVH_REALLOC) || \
299 !defined(PICO_BVH_FREE)
300 #include <stdlib.h>
301 #define PICO_BVH_CALLOC(num, size) (calloc(num, size))
302 #define PICO_BVH_REALLOC(ptr, size) (realloc(ptr, size))
303 #define PICO_BVH_FREE(ptr) (free(ptr))
304#endif
305
306#ifndef PICO_BVH_MEMSET
307 #include <string.h>
308 #define PICO_BVH_MEMSET memset
309#endif
310
311#ifndef PICO_BVH_MEMCPY
312 #include <string.h>
313 #define PICO_BVH_MEMCPY memcpy
314#endif
315
316#ifndef PICO_BVH_STACK_SIZE
317 #define PICO_BVH_STACK_SIZE 1024
318#endif
319
320#define BVH_IS_LEAF(n) ((n)->child[0] == BVH_NULL_ID)
321#define BVH_INITIAL_CAPACITY 64
322#define PICO_BVH_EPSILON 1e-9f
323
324// --- Internal Node -----------------------------------------------------------
325
326typedef struct
327{
328 bvh_aabb_t aabb;
329 int parent;
330 int child[2]; // BVH_NULL_ID for leaves
331 int height; // 0 = leaf
332 bvh_udata_t udata; // Valid only for leaves
333 bool allocated;
334} bvh_node_t;
335
336// --- Tree Structure ----------------------------------------------------------
337
338struct bvh_t
339{
340 bvh_node_t* nodes;
341 int capacity;
342 int root;
343 int free_list; // Singly-linked free list via child[0]
344 int leaf_count;
345};
346
347// --- Best-Sibling Heap Types -------------------------------------------------
348
349typedef struct
350{
351 int id;
352 float inherited_cost;
353} bvh_heap_entry_t;
354
355typedef struct
356{
357 bvh_heap_entry_t* data;
358 int size;
359 int cap;
360} bvh_min_heap_t;
361
362// --- Forward Declarations ----------------------------------------------------
363
364static inline bvh_aabb_t bvh_aabb_pad(bvh_aabb_t a, float m);
365static inline bvh_aabb_t bvh_aabb_union(bvh_aabb_t a, bvh_aabb_t b);
366static inline float bvh_aabb_perimeter(bvh_aabb_t a);
367static inline bool bvh_aabb_overlaps(bvh_aabb_t a, bvh_aabb_t b);
368static inline bool bvh_aabb_contains(bvh_aabb_t outer, bvh_aabb_t inner);
369static void bvh_grow(bvh_t* t);
370static int bvh_alloc_node(bvh_t* t);
371static void bvh_free_node(bvh_t* t, int id);
372static void bvh_refit(bvh_t* t, int id);
373static void bvh_rotate(bvh_t* t, int a_id);
374static void bvh_refit_and_rotate(bvh_t* t, int start);
375static void bvh_heap_push(bvh_min_heap_t* h, bvh_heap_entry_t e);
376static bvh_heap_entry_t bvh_heap_pop(bvh_min_heap_t* h);
377static int bvh_best_sibling(bvh_t* t, bvh_aabb_t new_aabb);
378static bool bvh_ray_aabb(bvh_vec2_t origin, bvh_vec2_t inv_dir, bvh_aabb_t aabb, float t_max);
379static void bvh_walk_rec(const bvh_t* t, int id, int depth, bvh_walk_cb cb, void* ctx);
380static float bvh_get_cost_rec(const bvh_t* t, int id);
381
382// --- Public: Lifecycle -------------------------------------------------------
383
384bvh_t* bvh_create(void)
385{
386 bvh_t* t = (bvh_t*)PICO_BVH_CALLOC(1, sizeof(bvh_t));
387 PICO_BVH_ASSERT(t);
388
389 t->capacity = BVH_INITIAL_CAPACITY;
390 t->nodes = (bvh_node_t*)PICO_BVH_CALLOC((size_t)t->capacity, sizeof(bvh_node_t));
391
392 PICO_BVH_ASSERT(t->nodes);
393
394 t->root = BVH_NULL_ID;
395 t->leaf_count = 0;
396
397 // Build free list
398 for (int i = 0; i < t->capacity - 1; ++i)
399 {
400 t->nodes[i].child[0] = i + 1;
401 }
402
403 t->nodes[t->capacity - 1].child[0] = BVH_NULL_ID;
404 t->free_list = 0;
405
406 return t;
407}
408
409void bvh_destroy(bvh_t* t)
410{
411 if (!t)
412 {
413 return;
414 }
415
416 PICO_BVH_FREE(t->nodes);
417 PICO_BVH_FREE(t);
418}
419
420// --- Public: Insert ----------------------------------------------------------
421
422int bvh_insert(bvh_t* t, bvh_aabb_t aabb, float padding, bvh_udata_t udata)
423{
424 int leaf_id = bvh_alloc_node(t);
425 bvh_node_t* leaf = &t->nodes[leaf_id];
426 leaf->aabb = padding > 0.f ? bvh_aabb_pad(aabb, padding) : aabb;
427 leaf->udata = udata;
428 leaf->height = 0;
429 t->leaf_count++;
430
431 // Empty tree
432 if (t->root == BVH_NULL_ID)
433 {
434 t->root = leaf_id;
435 leaf->parent = BVH_NULL_ID;
436 return leaf_id;
437 }
438
439 // Find best sibling
440 int sib_id = bvh_best_sibling(t, leaf->aabb);
441
442 // Create a new internal node to replace sibling
443 int new_id = bvh_alloc_node(t);
444 bvh_node_t* new_node = &t->nodes[new_id];
445 int old_parent = t->nodes[sib_id].parent;
446
447 new_node->parent = old_parent;
448 new_node->child[0] = sib_id;
449 new_node->child[1] = leaf_id;
450 new_node->height = 0; // Will be set by bvh_refit
451
452 t->nodes[sib_id].parent = new_id;
453 t->nodes[leaf_id].parent = new_id;
454
455 if (old_parent == BVH_NULL_ID)
456 {
457 t->root = new_id;
458 }
459 else
460 {
461 bvh_node_t* op = &t->nodes[old_parent];
462
463 if (op->child[0] == sib_id)
464 {
465 op->child[0] = new_id;
466 }
467 else
468 {
469 op->child[1] = new_id;
470 }
471 }
472
473 bvh_refit_and_rotate(t, new_id);
474
475 return leaf_id;
476}
477
478// --- Public: Remove ----------------------------------------------------------
479
480void bvh_remove(bvh_t* t, int leaf_id)
481{
482 PICO_BVH_ASSERT(leaf_id >= 0 && leaf_id < t->capacity);
483 PICO_BVH_ASSERT(BVH_IS_LEAF(&t->nodes[leaf_id]));
484
485 int parent_id = t->nodes[leaf_id].parent;
486 bvh_free_node(t, leaf_id);
487 t->leaf_count--;
488
489 if (parent_id == BVH_NULL_ID)
490 {
491 // Was the only node
492 t->root = BVH_NULL_ID;
493 return;
494 }
495
496 // Find the sibling, pull it up to replace the parent
497 bvh_node_t* parent = &t->nodes[parent_id];
498 int sibling = parent->child[0] == leaf_id
499 ? parent->child[1] : parent->child[0];
500
501 int grandparent = parent->parent;
502 bvh_free_node(t, parent_id);
503
504 t->nodes[sibling].parent = grandparent;
505
506 if (grandparent == BVH_NULL_ID)
507 {
508 t->root = sibling;
509 }
510 else
511 {
512 bvh_node_t* gp = &t->nodes[grandparent];
513
514 if (gp->child[0] == parent_id)
515 {
516 gp->child[0] = sibling;
517 }
518 else
519 {
520 gp->child[1] = sibling;
521 }
522
523 bvh_refit_and_rotate(t, grandparent);
524 }
525}
526
527// --- Public: Move ------------------------------------------------------------
528
529bool bvh_move(bvh_t* t, int leaf_id, bvh_aabb_t new_aabb, float padding)
530{
531 PICO_BVH_ASSERT(BVH_IS_LEAF(&t->nodes[leaf_id]));
532
533 bvh_aabb_t padded = padding > 0.f ? bvh_aabb_pad(new_aabb, padding) : new_aabb;
534
535 // No restructuring needed if the padded AABB still contains the new one
536 if (bvh_aabb_contains(t->nodes[leaf_id].aabb, new_aabb))
537 {
538 // Optionally shrink if the padded box is much bigger than needed
539 bvh_aabb_t big = bvh_aabb_pad(new_aabb, padding * 4.f);
540
541 if (bvh_aabb_contains(big, t->nodes[leaf_id].aabb))
542 {
543 return false;
544 }
545 }
546
547 bvh_udata_t ud = t->nodes[leaf_id].udata;
548 bvh_remove(t, leaf_id);
549
550 // bvh_remove freed leaf_id (and its parent). Ensure leaf_id sits at the
551 // front of the free list so bvh_alloc_node returns it first, preserving
552 // the caller's handle.
553 if (t->free_list != leaf_id)
554 {
555 int prev = t->free_list;
556
557 while (t->nodes[prev].child[0] != leaf_id)
558 {
559 prev = t->nodes[prev].child[0];
560 }
561
562 t->nodes[prev].child[0] = t->nodes[leaf_id].child[0];
563 t->nodes[leaf_id].child[0] = t->free_list;
564 t->free_list = leaf_id;
565 }
566
567 int new_id = bvh_insert(t, padded, 0.f, ud);
568 (void)new_id;
569 PICO_BVH_ASSERT(new_id == leaf_id);
570
571 return true;
572}
573
574// --- Public: Query (AABB) ----------------------------------------------------
575
576// Iterative DFS using an explicit stack to avoid recursion overhead.
577void bvh_query_aabb(const bvh_t* t, bvh_aabb_t query, bvh_query_cb cb, void* ctx)
578{
579 if (t->root == BVH_NULL_ID)
580 {
581 return;
582 }
583
584 // Stack: fixed-size. 2*height+2 suffices for a balanced tree.
585 // We allocate generously; could also be dynamic.
586 int stack[PICO_BVH_STACK_SIZE];
587 int top = 0;
588 stack[top++] = t->root;
589
590 while (top > 0)
591 {
592 int id = stack[--top];
593 if (id == BVH_NULL_ID)
594 {
595 continue;
596 }
597
598 const bvh_node_t* n = &t->nodes[id];
599
600 if (!bvh_aabb_overlaps(n->aabb, query))
601 {
602 continue;
603 }
604
605 if (BVH_IS_LEAF(n))
606 {
607 if (!cb(id, n->udata, ctx))
608 {
609 return;
610 }
611 }
612 else
613 {
614 PICO_BVH_ASSERT(top + 2 <= PICO_BVH_STACK_SIZE);
615 stack[top++] = n->child[0];
616 stack[top++] = n->child[1];
617 }
618 }
619}
620
621// --- Public: Query (Ray) -----------------------------------------------------
622
623void bvh_query_ray(const bvh_t* t,
624 bvh_vec2_t origin, bvh_vec2_t dir, float t_max,
625 bvh_query_cb cb, void* ctx)
626{
627 if (t->root == BVH_NULL_ID)
628 {
629 return;
630 }
631
632 // Precompute reciprocal direction (handle zero-components)
633 bvh_vec2_t inv_dir =
634 {
635 fabsf(dir.x) > PICO_BVH_EPSILON ? 1.f / dir.x : (dir.x >= 0.f ? FLT_MAX : -FLT_MAX),
636 fabsf(dir.y) > PICO_BVH_EPSILON ? 1.f / dir.y : (dir.y >= 0.f ? FLT_MAX : -FLT_MAX)
637 };
638
639 int stack[PICO_BVH_STACK_SIZE]; // TODO: define constant
640 int top = 0;
641 stack[top++] = t->root;
642
643 while (top > 0)
644 {
645 int id = stack[--top];
646 if (id == BVH_NULL_ID)
647 {
648 continue;
649 }
650
651 const bvh_node_t* n = &t->nodes[id];
652
653 if (!bvh_ray_aabb(origin, inv_dir, n->aabb, t_max))
654 {
655 continue;
656 }
657
658 if (BVH_IS_LEAF(n))
659 {
660 if (!cb(id, n->udata, ctx))
661 {
662 return;
663 }
664 }
665 else
666 {
667 PICO_BVH_ASSERT(top + 2 <= PICO_BVH_STACK_SIZE);
668 stack[top++] = n->child[0];
669 stack[top++] = n->child[1];
670 }
671 }
672}
673
674// --- Public: Accessors -------------------------------------------------------
675
676bvh_udata_t bvh_get_udata(const bvh_t* t, int leaf_id)
677{
678 PICO_BVH_ASSERT(BVH_IS_LEAF(&t->nodes[leaf_id]));
679 return t->nodes[leaf_id].udata;
680}
681
682bvh_aabb_t bvh_get_padded_aabb(const bvh_t* t, int leaf_id)
683{
684 return t->nodes[leaf_id].aabb;
685}
686
687int bvh_get_leaf_count(const bvh_t* t) { return t->leaf_count; }
688
689// --- Public: Walk ------------------------------------------------------------
690
691void bvh_walk(const bvh_t* t, bvh_walk_cb cb, void* ctx)
692{
693 bvh_walk_rec(t, t->root, 0, cb, ctx);
694}
695
696// --- Public: Cost ------------------------------------------------------------
697
698float bvh_get_cost(const bvh_t* t)
699{
700 return bvh_get_cost_rec(t, t->root);
701}
702
703// --- Math Primitives ---------------------------------------------------------
704
705bvh_aabb_t bvh_make_aabb(float x, float y, float w, float h)
706{
707 return (bvh_aabb_t){ {x, y}, {x + w, y + h} };
708}
709
710static inline bvh_aabb_t bvh_aabb_pad(bvh_aabb_t a, float m)
711{
712 return (bvh_aabb_t){ {a.min.x - m, a.min.y - m}, {a.max.x + m, a.max.y + m} };
713}
714
715static inline bvh_aabb_t bvh_aabb_union(bvh_aabb_t a, bvh_aabb_t b)
716{
717 return (bvh_aabb_t){
718 { a.min.x < b.min.x ? a.min.x : b.min.x,
719 a.min.y < b.min.y ? a.min.y : b.min.y },
720 { a.max.x > b.max.x ? a.max.x : b.max.x,
721 a.max.y > b.max.y ? a.max.y : b.max.y }
722 };
723}
724
725static inline float bvh_aabb_perimeter(bvh_aabb_t a)
726{
727 return 2.f * ((a.max.x - a.min.x) + (a.max.y - a.min.y));
728}
729
730static inline bool bvh_aabb_overlaps(bvh_aabb_t a, bvh_aabb_t b)
731{
732 return a.min.x <= b.max.x && a.max.x >= b.min.x
733 && a.min.y <= b.max.y && a.max.y >= b.min.y;
734}
735
736static inline bool bvh_aabb_contains(bvh_aabb_t outer, bvh_aabb_t inner)
737{
738 return outer.min.x <= inner.min.x && inner.max.x <= outer.max.x
739 && outer.min.y <= inner.min.y && inner.max.y <= outer.max.y;
740}
741
742// --- Node Pool ---------------------------------------------------------------
743
744static void bvh_grow(bvh_t* t)
745{
746 int old_cap = t->capacity;
747 int new_cap = old_cap * 2;
748 t->nodes = (bvh_node_t*)PICO_BVH_REALLOC(t->nodes, (size_t)new_cap * sizeof(bvh_node_t));
749
750 PICO_BVH_ASSERT(t->nodes);
751
752 PICO_BVH_MEMSET(t->nodes + old_cap, 0, (size_t)(new_cap - old_cap) * sizeof(bvh_node_t));
753
754 // Chain new slots onto the free list
755 for (int i = old_cap; i < new_cap - 1; ++i)
756 {
757 t->nodes[i].child[0] = i + 1;
758 }
759
760 t->nodes[new_cap - 1].child[0] = t->free_list;
761 t->free_list = old_cap;
762 t->capacity = new_cap;
763}
764
765static int bvh_alloc_node(bvh_t* t)
766{
767 if (t->free_list == BVH_NULL_ID)
768 {
769 bvh_grow(t);
770 }
771
772 int id = t->free_list;
773 t->free_list = t->nodes[id].child[0];
774 bvh_node_t* n = &t->nodes[id];
775 n->parent = BVH_NULL_ID;
776 n->child[0] = BVH_NULL_ID;
777 n->child[1] = BVH_NULL_ID;
778 n->height = 0;
779 n->udata = 0;
780 n->allocated = true;
781 return id;
782}
783
784static void bvh_free_node(bvh_t* t, int id)
785{
786 PICO_BVH_ASSERT(id >= 0 && id < t->capacity);
787 t->nodes[id].allocated = false;
788 t->nodes[id].child[0] = t->free_list;
789 t->free_list = id;
790}
791
792// --- Helpers -----------------------------------------------------------------
793
794static void bvh_refit(bvh_t* t, int id)
795{
796 bvh_node_t* n = &t->nodes[id];
797 n->aabb = bvh_aabb_union(t->nodes[n->child[0]].aabb,
798 t->nodes[n->child[1]].aabb);
799 int h0 = t->nodes[n->child[0]].height;
800 int h1 = t->nodes[n->child[1]].height;
801 n->height = 1 + (h0 > h1 ? h0 : h1);
802}
803
804// --- SAH Rotation ------------------------------------------------------------
805/*
806 Local tree at A before rotation:
807
808 A
809 / \
810 B C
811 / \ / \
812 b0 b1 c0 c1
813
814 We evaluate 4 one-step rotations (swapping a grandchild with the opposite
815 subtree) and keep only the one that reduces perimeter(A) the most. Only two
816 cases are considered for brevity.
817
818 Candidate 0 (costs[0]): swap b0 <-> C
819
820 A
821 / \
822 B b0
823 / \
824 C b1
825 / \
826 c0 c1
827
828
829 Candidate 2 (costs[2]): swap c0 <-> B
830
831 A
832 / \
833 c0 C
834 / \
835 B c1
836 / \
837 b0 b11
838
839 We evaluate all valid candidates and apply the one that gives the largest
840 reduction in perimeter(A). If none improves cost, no rotation is applied.
841 */
842static void bvh_rotate(bvh_t* t, int a_id)
843{
844 bvh_node_t* A = &t->nodes[a_id];
845
846 if (A->height < 2)
847 {
848 return;
849 }
850
851 int b_id = A->child[0];
852 int c_id = A->child[1];
853 bvh_node_t* B = &t->nodes[b_id];
854 bvh_node_t* C = &t->nodes[c_id];
855
856 float base_cost = bvh_aabb_perimeter(A->aabb);
857
858 // Candidate costs
859 float costs[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
860
861 if (!BVH_IS_LEAF(B))
862 {
863 costs[0] = bvh_aabb_perimeter(bvh_aabb_union(C->aabb,
864 t->nodes[B->child[1]].aabb)); // Swap b0 <-> C
865 costs[1] = bvh_aabb_perimeter(bvh_aabb_union(C->aabb,
866 t->nodes[B->child[0]].aabb)); // Swap b1 <-> C
867 }
868
869 if (!BVH_IS_LEAF(C))
870 {
871 costs[2] = bvh_aabb_perimeter(bvh_aabb_union(B->aabb,
872 t->nodes[C->child[1]].aabb)); // Swap c0 <-> B
873 costs[3] = bvh_aabb_perimeter(bvh_aabb_union(B->aabb,
874 t->nodes[C->child[0]].aabb)); // Swap c1 <-> B
875 }
876
877 // Find best candidate
878 int best = -1;
879 float best_cost = base_cost;
880
881 for (int i = 0; i < 4; ++i)
882 {
883 if (costs[i] < best_cost)
884 {
885 best_cost = costs[i];
886 best = i;
887 }
888 }
889
890 if (best < 0)
891 {
892 return; // No improvement
893 }
894
895 switch (best)
896 {
897 case 0:
898 { // Swap B->child[0] <-> C
899 int x = B->child[0];
900 B->child[0] = c_id;
901 t->nodes[c_id].parent = b_id;
902 A->child[1] = x;
903 t->nodes[x].parent = a_id;
904 bvh_refit(t, b_id);
905 bvh_refit(t, a_id);
906 break;
907 }
908
909 case 1:
910 { // Swap B->child[1] <-> C
911 int x = B->child[1];
912 B->child[1] = c_id;
913 t->nodes[c_id].parent = b_id;
914 A->child[1] = x;
915 t->nodes[x].parent = a_id;
916 bvh_refit(t, b_id);
917 bvh_refit(t, a_id);
918 break;
919 }
920
921 case 2:
922 { // Swap C->child[0] <-> B
923 int x = C->child[0];
924 C->child[0] = b_id;
925 t->nodes[b_id].parent = c_id;
926 A->child[0] = x;
927 t->nodes[x].parent = a_id;
928 bvh_refit(t, c_id);
929 bvh_refit(t, a_id);
930 break;
931 }
932
933 case 3:
934 { // Swap C->child[1] <-> B
935 int x = C->child[1];
936 C->child[1] = b_id;
937 t->nodes[b_id].parent = c_id;
938 A->child[0] = x;
939 t->nodes[x].parent = a_id;
940 bvh_refit(t, c_id);
941 bvh_refit(t, a_id);
942 break;
943 }
944 }
945}
946
947// Walk from `start` toward the root: bvh_refit + bvh_rotate each ancestor.
948static void bvh_refit_and_rotate(bvh_t* t, int start)
949{
950 int id = start;
951 while (id != BVH_NULL_ID)
952 {
953 if (!BVH_IS_LEAF(&t->nodes[id]))
954 {
955 bvh_refit(t, id);
956 }
957
958 bvh_rotate(t, id);
959 id = t->nodes[id].parent;
960 }
961}
962
963// --- Best-Sibling Search (SAH) -----------------------------------------------
964/*
965 Branch-and-bound traversal to find the node that, when used as a sibling
966 for the new leaf nl, minimises the total induced cost increase up to root.
967
968 induced_cost(node) = cost(union(node, nl)) - cost(node)
969 + sum of [cost(union(anc, nl)) - cost(anc)] for each ancestor
970
971 We maintain a priority queue (simple binary heap) keyed on a lower bound
972 of the induced cost to prune branches early.
973 */
974static void bvh_heap_push(bvh_min_heap_t* h, bvh_heap_entry_t entry)
975{
976 if (h->size == h->cap)
977 {
978 h->cap = h->cap ? h->cap * 2 : 16;
979 h->data = (bvh_heap_entry_t*)PICO_BVH_REALLOC(h->data,
980 (size_t)h->cap * sizeof(bvh_heap_entry_t));
981
982 PICO_BVH_ASSERT(h->data);
983 }
984
985 PICO_BVH_ASSERT(h->size < h->cap);
986
987 // Sift-up
988 int i = h->size++;
989
990 while (i > 0)
991 {
992 int parent = (i - 1) / 2;
993
994 if (h->data[parent].inherited_cost <= entry.inherited_cost)
995 {
996 break;
997 }
998
999 h->data[i] = h->data[parent];
1000 i = parent;
1001 }
1002
1003 h->data[i] = entry;
1004}
1005
1006static bvh_heap_entry_t bvh_heap_pop(bvh_min_heap_t* h)
1007{
1008 PICO_BVH_ASSERT(h->size > 0);
1009
1010 bvh_heap_entry_t top = h->data[0];
1011 bvh_heap_entry_t last = h->data[--h->size];
1012
1013 if (h->size == 0)
1014 {
1015 return top;
1016 }
1017
1018 int i = 0;
1019
1020 while (true)
1021 {
1022 int left = i * 2 + 1;
1023
1024 if (left >= h->size)
1025 {
1026 break;
1027 }
1028
1029 int right = left + 1;
1030 int child = left;
1031
1032 if (right < h->size
1033 && h->data[right].inherited_cost < h->data[left].inherited_cost)
1034 {
1035 child = right;
1036 }
1037
1038 if (h->data[child].inherited_cost >= last.inherited_cost)
1039 {
1040 break;
1041 }
1042
1043 h->data[i] = h->data[child];
1044 i = child;
1045 }
1046
1047 h->data[i] = last;
1048 return top;
1049}
1050
1051static int bvh_best_sibling(bvh_t* t, bvh_aabb_t new_aabb)
1052{
1053 float new_cost = bvh_aabb_perimeter(new_aabb);
1054
1055 bvh_min_heap_t heap = {0};
1056 bvh_heap_push(&heap, (bvh_heap_entry_t){ t->root, 0.f });
1057
1058 int best_id = t->root;
1059 float best_cost = FLT_MAX;
1060
1061 while (heap.size > 0)
1062 {
1063 bvh_heap_entry_t entry = bvh_heap_pop(&heap);
1064
1065 if (entry.inherited_cost >= best_cost)
1066 {
1067 break; // Prune
1068 }
1069
1070 bvh_node_t* node = &t->nodes[entry.id];
1071 bvh_aabb_t combined = bvh_aabb_union(node->aabb, new_aabb);
1072 float direct_cost = bvh_aabb_perimeter(combined);
1073 float total_cost = direct_cost + entry.inherited_cost;
1074
1075 if (total_cost < best_cost)
1076 {
1077 best_cost = total_cost;
1078 best_id = entry.id;
1079 }
1080
1081 if (!BVH_IS_LEAF(node))
1082 {
1083 // Inherited cost that any descendant must pay
1084 float inherited_cost = entry.inherited_cost + direct_cost
1085 - bvh_aabb_perimeter(node->aabb);
1086
1087 float lower_bound = inherited_cost + new_cost; // <= P(union(child, new)) + inherited
1088
1089 if (lower_bound < best_cost)
1090 {
1091 bvh_heap_push(&heap, (bvh_heap_entry_t){ node->child[0], inherited_cost });
1092 bvh_heap_push(&heap, (bvh_heap_entry_t){ node->child[1], inherited_cost });
1093 }
1094 }
1095 }
1096
1097 PICO_BVH_FREE(heap.data); // TODO: store heap in bvh_t
1098 return best_id;
1099}
1100
1101// --- Ray Test ----------------------------------------------------------------
1102
1103//Slab test for ray vs AABB intersection.
1104//Returns true if the ray hits the AABB within [0, t_max].
1105static bool bvh_ray_aabb(bvh_vec2_t origin, bvh_vec2_t inv_dir, bvh_aabb_t aabb, float t_max)
1106{
1107 float tx1 = (aabb.min.x - origin.x) * inv_dir.x;
1108 float tx2 = (aabb.max.x - origin.x) * inv_dir.x;
1109 float tmin = tx1 < tx2 ? tx1 : tx2;
1110 float tmax = tx1 > tx2 ? tx1 : tx2;
1111
1112 float ty1 = (aabb.min.y - origin.y) * inv_dir.y;
1113 float ty2 = (aabb.max.y - origin.y) * inv_dir.y;
1114 float tymin = ty1 < ty2 ? ty1 : ty2;
1115 float tymax = ty1 > ty2 ? ty1 : ty2;
1116
1117 tmin = tmin > tymin ? tmin : tymin;
1118 tmax = tmax < tymax ? tmax : tymax;
1119
1120 return tmax >= 0.f && tmin <= tmax && tmin <= t_max;
1121}
1122
1123// --- Walk Helper -------------------------------------------------------------
1124
1125static void bvh_walk_rec(const bvh_t* t, int id, int depth, bvh_walk_cb cb, void* ctx)
1126{
1127 if (id == BVH_NULL_ID)
1128 {
1129 return;
1130 }
1131
1132 const bvh_node_t* n = &t->nodes[id];
1133 cb(n->aabb, depth, BVH_IS_LEAF(n), n->udata, ctx);
1134
1135 if (!BVH_IS_LEAF(n))
1136 {
1137 bvh_walk_rec(t, n->child[0], depth + 1, cb, ctx);
1138 bvh_walk_rec(t, n->child[1], depth + 1, cb, ctx);
1139 }
1140}
1141
1142// --- Cost Helper -------------------------------------------------------------
1143
1144static float bvh_get_cost_rec(const bvh_t* t, int id)
1145{
1146 if (id == BVH_NULL_ID)
1147 {
1148 return 0.f;
1149 }
1150
1151 const bvh_node_t* n = &t->nodes[id];
1152 float c = bvh_aabb_perimeter(n->aabb);
1153
1154 if (!BVH_IS_LEAF(n))
1155 {
1156 c += bvh_get_cost_rec(t, n->child[0]) + bvh_get_cost_rec(t, n->child[1]);
1157 }
1158
1159 return c;
1160}
1161
1162#endif // PICO_BVH_IMPLEMENTATION
1163
1164/*
1165 ---------------------------------------------------------------------------
1166 This software is available under two licenses (A) or (B). You may choose
1167 either one as you wish:
1168 ---------------------------------------------------------------------------
1169
1170 (A) The MIT License
1171
1172 Copyright (c) 2026 James McLean
1173
1174 Permission is hereby granted, free of charge, to any person obtaining a copy
1175 of this software and associated documentation files (the "Software"), to
1176 deal in the Software without restriction, including without limitation the
1177 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
1178 sell copies of the Software, and to permit persons to whom the Software is
1179 furnished to do so, subject to the following conditions:
1180
1181 The above copyright notice and this permission notice shall be included in
1182 all copies or substantial portions of the Software.
1183
1184 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1185 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1186 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1187 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1188 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1189 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
1190 IN THE SOFTWARE.
1191
1192 ---------------------------------------------------------------------------
1193
1194 (B) Public Domain (www.unlicense.org)
1195
1196 This is free and unencumbered software released into the public domain.
1197
1198 Anyone is free to copy, modify, publish, use, compile, sell, or distribute
1199 this software, either in source code form or as a compiled binary, for any
1200 purpose, commercial or non-commercial, and by any means.
1201
1202 In jurisdictions that recognize copyright laws, the author or authors of
1203 this software dedicate any and all copyright interest in the software to the
1204 public domain. We make this dedication for the benefit of the public at
1205 large and to the detriment of our heirs and successors. We intend this
1206 dedication to be an overt act of relinquishment in perpetuity of all present
1207 and future rights to this software under copyright law.
1208
1209 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1210 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1211 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1212 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1213 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1214 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1215*/
void bvh_query_ray(const bvh_t *tree, bvh_vec2_t origin, bvh_vec2_t dir, float t_max, bvh_query_cb cb, void *ctx)
Queries the tree against a ray.
void bvh_destroy(bvh_t *tree)
Destroys and deallocates a BVH instance.
void bvh_query_aabb(const bvh_t *tree, bvh_aabb_t query, bvh_query_cb cb, void *ctx)
Queries the tree against an AABB.
bvh_udata_t bvh_get_udata(const bvh_t *tree, int leaf_id)
Returns the user data from the specified leaf node.
bool bvh_move(bvh_t *tree, int leaf_id, bvh_aabb_t new_aabb, float padding)
Update a leaf's AABB.
float bvh_get_cost(const bvh_t *tree)
Total surface-area cost (lower = better balanced).
int bvh_get_leaf_count(const bvh_t *tree)
Returns number of leaves in the tree.
struct bvh_t bvh_t
BVH instance.
Definition pico_bvh.h:124
bvh_aabb_t bvh_make_aabb(float x, float y, float w, float h)
Constructs an AABB from a position and dimensions.
bvh_aabb_t bvh_get_padded_aabb(const bvh_t *tree, int leaf_id)
Returns the enlarged bounds from the specified leaf node.
void bvh_remove(bvh_t *tree, int leaf_id)
Remove a leaf.
#define BVH_NULL_ID
Definition pico_bvh.h:99
void(* bvh_walk_cb)(bvh_aabb_t aabb, int depth, bool is_leaf, bvh_udata_t udata, void *ctx)
Called for every node during bvh_walk(); depth=0 at root.
Definition pico_bvh.h:118
PICO_BVH_UDATA_TYPE bvh_udata_t
Definition pico_bvh.h:81
void bvh_walk(const bvh_t *tree, bvh_walk_cb cb, void *ctx)
Depth-first walk over every node (internal + leaf).
bvh_t * bvh_create(void)
Allocates and initializes a BVH instances.
int bvh_insert(bvh_t *tree, bvh_aabb_t aabb, float padding, bvh_udata_t udata)
Inserts a new leaf.
bool(* bvh_query_cb)(int leaf_id, bvh_udata_t udata, void *ctx)
Return false to stop traversal early.
Definition pico_bvh.h:108
#define PICO_BVH_UDATA_TYPE
Definition pico_bvh.h:78
Definition pico_bvh.h:92
bvh_vec2_t max
Definition pico_bvh.h:94
bvh_vec2_t min
Definition pico_bvh.h:93
Definition pico_bvh.h:86
float x
Definition pico_bvh.h:87
float y
Definition pico_bvh.h:88