summaryrefslogtreecommitdiff
path: root/modules/sparkler/bsp.c
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@gnugeneration.com>2017-01-18 17:14:52 -0800
committerVito Caputo <vcaputo@gnugeneration.com>2017-01-18 17:31:44 -0800
commit524db0cf19648e3c7c78d3e73103b7a0bdcd6bfc (patch)
tree6fd682629904a210927797c92d956c208666b03a /modules/sparkler/bsp.c
parentee2073d4e411555aba878277131b56f7eb562c84 (diff)
*: move source into src/ subdir
Restoring some organizational sanity since adopting autotools.
Diffstat (limited to 'modules/sparkler/bsp.c')
-rw-r--r--modules/sparkler/bsp.c584
1 files changed, 0 insertions, 584 deletions
diff --git a/modules/sparkler/bsp.c b/modules/sparkler/bsp.c
deleted file mode 100644
index 381e922..0000000
--- a/modules/sparkler/bsp.c
+++ /dev/null
@@ -1,584 +0,0 @@
-#include <assert.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "bsp.h"
-
-
-/* octree-based bsp for faster proximity searches */
-/* meanings:
- * octrant = "octo" analog of a quadrant, an octree is a quadtree with an additional dimension (Z/3d)
- * bv = bounding volume
- * bsp = binary space partition
- * occupant = the things being indexed by the bsp (e.g. a particle, or its position)
- */
-
-
-/* FIXME: these are not tuned at all, and should really all be parameters to bsp_new() instead */
-#define BSP_GROWBY 16
-#define BSP_MAX_OCCUPANTS 64
-#define BSP_MAX_DEPTH 16
-
-#define MAX(_a, _b) (_a > _b ? _a : _b)
-#define MIN(_a, _b) (_a < _b ? _a : _b)
-
-
-struct bsp_node_t {
- v3f_t center; /* center point about which the bounding volume's 3d-space is divided */
- bsp_node_t *parent; /* parent bounding volume, NULL when root node */
- bsp_node_t *octrants; /* NULL when a leaf, otherwise an array of 8 bsp_node_t's */
- list_head_t occupants; /* list of occupants in this volume when a leaf node */
- unsigned n_occupants; /* number of ^^ */
-};
-
-#define OCTRANTS \
- octrant(OCT_XL_YL_ZL, (1 << 2 | 1 << 1 | 1)) \
- octrant(OCT_XR_YL_ZL, ( 1 << 1 | 1)) \
- octrant(OCT_XL_YR_ZL, (1 << 2 | 1)) \
- octrant(OCT_XR_YR_ZL, ( 1)) \
- octrant(OCT_XL_YL_ZR, (1 << 2 | 1 << 1 )) \
- octrant(OCT_XR_YL_ZR, ( 1 << 1 )) \
- octrant(OCT_XL_YR_ZR, (1 << 2 )) \
- octrant(OCT_XR_YR_ZR, 0)
-
-#define octrant(_sym, _val) _sym = _val,
-typedef enum _octrant_idx_t {
- OCTRANTS
-} octrant_idx_t;
-#undef octrant
-
-/* bsp lookup state, encapsulated for preservation across composite
- * lookup-dependent operations, so they can potentially avoid having
- * to redo the lookup. i.e. lookup caching.
- */
-typedef struct _bsp_lookup_t {
- int depth;
- v3f_t left;
- v3f_t right;
- bsp_node_t *bv;
- octrant_idx_t oidx;
-} bsp_lookup_t;
-
-struct bsp_t {
- bsp_node_t root;
- list_head_t free;
- bsp_lookup_t lookup_cache;
-};
-
-
-static inline const char * octstr(octrant_idx_t oidx)
-{
-#define octrant(_sym, _val) #_sym,
- static const char *octrant_strs[] = {
- OCTRANTS
- };
-#undef octrant
-
- return octrant_strs[oidx];
-}
-
-
-static inline void _bsp_print(bsp_node_t *node)
-{
- static int depth = 0;
-
- fprintf(stderr, "%-*s %i: %p\n", depth, " ", depth, node);
- if (node->octrants) {
- int i;
-
- for (i = 0; i < 8; i++) {
- fprintf(stderr, "%-*s %i: %s: %p\n", depth, " ", depth, octstr(i), &node->octrants[i]);
- depth++;
- _bsp_print(&node->octrants[i]);
- depth--;
- }
- }
-}
-
-
-/* Print a bsp tree to stderr (debugging) */
-void bsp_print(bsp_t *bsp)
-{
- _bsp_print(&bsp->root);
-}
-
-
-/* Initialize the lookup cache to the root */
-static inline void bsp_init_lookup_cache(bsp_t *bsp) {
- bsp->lookup_cache.bv = &bsp->root;
- bsp->lookup_cache.depth = 0;
- v3f_set(&bsp->lookup_cache.left, -1.0, -1.0, -1.0); /* TODO: the bsp AABB should be supplied to bsp_new() */
- v3f_set(&bsp->lookup_cache.right, 1.0, 1.0, 1.0);
-}
-
-
-/* Invalidate/reset the bsp's lookup cache TODO: make conditional on a supplied node being cached? */
-static inline void bsp_invalidate_lookup_cache(bsp_t *bsp) {
- if (bsp->lookup_cache.bv != &bsp->root) {
- bsp_init_lookup_cache(bsp);
- }
-}
-
-
-/* Create a new bsp octree. */
-bsp_t * bsp_new(void)
-{
- bsp_t *bsp;
-
- bsp = calloc(1, sizeof(bsp_t));
- if (!bsp) {
- return NULL;
- }
-
- INIT_LIST_HEAD(&bsp->root.occupants);
- INIT_LIST_HEAD(&bsp->free);
- bsp_init_lookup_cache(bsp);
-
- return bsp;
-}
-
-
-/* Free a bsp octree */
-void bsp_free(bsp_t *bsp)
-{
- /* TODO: free everything ... */
- free(bsp);
-}
-
-
-/* lookup a position's containing leaf node in the bsp tree, store resultant lookup state in *lookup_res */
-static inline void bsp_lookup_position(bsp_t *bsp, bsp_node_t *root, v3f_t *position, bsp_lookup_t *lookup_res)
-{
- bsp_lookup_t res = bsp->lookup_cache;
-
- if (res.bv->parent) {
- /* When starting from a cached (non-root) lookup, we must verify our position falls within the cached bv */
- if (position->x < res.left.x || position->x > res.right.x ||
- position->y < res.left.y || position->y > res.right.y ||
- position->z < res.left.z || position->z > res.right.z) {
- bsp_invalidate_lookup_cache(bsp);
- res = bsp->lookup_cache;
- }
- }
-
- while (res.bv->octrants) {
- res.oidx = OCT_XR_YR_ZR;
- if (position->x <= res.bv->center.x) {
- res.oidx |= (1 << 2);
- res.right.x = res.bv->center.x;
- } else {
- res.left.x = res.bv->center.x;
- }
-
- if (position->y <= res.bv->center.y) {
- res.oidx |= (1 << 1);
- res.right.y = res.bv->center.y;
- } else {
- res.left.y = res.bv->center.y;
- }
-
- if (position->z <= res.bv->center.z) {
- res.oidx |= 1;
- res.right.z = res.bv->center.z;
- } else {
- res.left.z = res.bv->center.z;
- }
-
- res.bv = &res.bv->octrants[res.oidx];
- res.depth++;
- }
-
- *lookup_res = bsp->lookup_cache = res;
-}
-
-
-/* Add an occupant to a bsp tree, use provided node lookup *l if supplied */
-static inline void _bsp_add_occupant(bsp_t *bsp, bsp_occupant_t *occupant, v3f_t *position, bsp_lookup_t *l)
-{
- bsp_lookup_t _lookup;
-
- /* if no explicitly cached lookup result was provided, perform the lookup now (which may still be cached). */
- if (!l) {
- l = &_lookup;
- bsp_lookup_position(bsp, &bsp->root, position, l);
- }
-
- assert(l);
- assert(l->bv);
-
- occupant->position = position;
-
-#define map_occupant2octrant(_occupant, _bv, _octrant) \
- _octrant = OCT_XR_YR_ZR; \
- if (_occupant->position->x <= _bv->center.x) { \
- _octrant |= (1 << 2); \
- } \
- if (_occupant->position->y <= _bv->center.y) { \
- _octrant |= (1 << 1); \
- } \
- if (_occupant->position->z <= _bv->center.z) { \
- _octrant |= 1; \
- }
-
- if (l->bv->n_occupants >= BSP_MAX_OCCUPANTS && l->depth < BSP_MAX_DEPTH) {
- int i;
- list_head_t *t, *_t;
- bsp_node_t *bv = l->bv;
-
- /* bv is full and shallow enough, subdivide it. */
-
- /* ensure the free list has something for us */
- if (list_empty(&bsp->free)) {
- bsp_node_t *t;
-
- /* TODO: does using the chunker instead make sense here? */
- t = calloc(sizeof(bsp_node_t), 8 * BSP_GROWBY);
- for (i = 0; i < 8 * BSP_GROWBY; i += 8) {
- list_add(&t[i].occupants, &bsp->free);
- }
- }
-
- /* take an octrants array from the free list */
- bv->octrants = list_entry(bsp->free.next, bsp_node_t, occupants);
- list_del(&bv->octrants[0].occupants);
-
- /* initialize the octrants */
- for (i = 0; i < 8; i++) {
- INIT_LIST_HEAD(&bv->octrants[i].occupants);
- bv->octrants[i].n_occupants = 0;
- bv->octrants[i].parent = bv;
- bv->octrants[i].octrants = NULL;
- }
-
- /* set the center point in each octrant which places the partitioning hyperplane */
- /* XXX: note this is pretty unreadable due to reusing the earlier computed values
- * where the identical computation is required.
- */
- bv->octrants[OCT_XR_YR_ZR].center.x = (l->right.x - bv->center.x) * .5f + bv->center.x;
- bv->octrants[OCT_XR_YR_ZR].center.y = (l->right.y - bv->center.y) * .5f + bv->center.y;
- bv->octrants[OCT_XR_YR_ZR].center.z = (l->right.z - bv->center.z) * .5f + bv->center.z;
-
- bv->octrants[OCT_XR_YR_ZL].center.x = bv->octrants[OCT_XR_YR_ZR].center.x;
- bv->octrants[OCT_XR_YR_ZL].center.y = bv->octrants[OCT_XR_YR_ZR].center.y;
- bv->octrants[OCT_XR_YR_ZL].center.z = (bv->center.z - l->left.z) * .5f + l->left.z;
-
- bv->octrants[OCT_XR_YL_ZR].center.x = bv->octrants[OCT_XR_YR_ZR].center.x;
- bv->octrants[OCT_XR_YL_ZR].center.y = (bv->center.y - l->left.y) * .5f + l->left.y;
- bv->octrants[OCT_XR_YL_ZR].center.z = bv->octrants[OCT_XR_YR_ZR].center.z;
-
- bv->octrants[OCT_XR_YL_ZL].center.x = bv->octrants[OCT_XR_YR_ZR].center.x;
- bv->octrants[OCT_XR_YL_ZL].center.y = bv->octrants[OCT_XR_YL_ZR].center.y;
- bv->octrants[OCT_XR_YL_ZL].center.z = bv->octrants[OCT_XR_YR_ZL].center.z;
-
- bv->octrants[OCT_XL_YR_ZR].center.x = (bv->center.x - l->left.x) * .5f + l->left.x;
- bv->octrants[OCT_XL_YR_ZR].center.y = bv->octrants[OCT_XR_YR_ZR].center.y;
- bv->octrants[OCT_XL_YR_ZR].center.z = bv->octrants[OCT_XR_YR_ZR].center.z;
-
- bv->octrants[OCT_XL_YR_ZL].center.x = bv->octrants[OCT_XL_YR_ZR].center.x;
- bv->octrants[OCT_XL_YR_ZL].center.y = bv->octrants[OCT_XR_YR_ZR].center.y;
- bv->octrants[OCT_XL_YR_ZL].center.z = bv->octrants[OCT_XR_YR_ZL].center.z;
-
- bv->octrants[OCT_XL_YL_ZR].center.x = bv->octrants[OCT_XL_YR_ZR].center.x;
- bv->octrants[OCT_XL_YL_ZR].center.y = bv->octrants[OCT_XR_YL_ZR].center.y;
- bv->octrants[OCT_XL_YL_ZR].center.z = bv->octrants[OCT_XR_YR_ZR].center.z;
-
- bv->octrants[OCT_XL_YL_ZL].center.x = bv->octrants[OCT_XL_YR_ZR].center.x;
- bv->octrants[OCT_XL_YL_ZL].center.y = bv->octrants[OCT_XR_YL_ZR].center.y;
- bv->octrants[OCT_XL_YL_ZL].center.z = bv->octrants[OCT_XR_YR_ZL].center.z;
-
- /* migrate the occupants into the appropriate octrants */
- list_for_each_safe(t, _t, &bv->occupants) {
- octrant_idx_t oidx;
- bsp_occupant_t *o = list_entry(t, bsp_occupant_t, occupants);
-
- map_occupant2octrant(o, bv, oidx);
- list_move(t, &bv->octrants[oidx].occupants);
- o->leaf = &bv->octrants[oidx];
- bv->octrants[oidx].n_occupants++;
- }
- bv->n_occupants = 0;
-
- /* a new leaf assumes the bv position for the occupant to be added into */
- map_occupant2octrant(occupant, bv, l->oidx);
- l->bv = &bv->octrants[l->oidx];
- l->depth++;
- }
-
-#undef map_occupant2octrant
-
- occupant->leaf = l->bv;
- list_add(&occupant->occupants, &l->bv->occupants);
- l->bv->n_occupants++;
-
- assert(occupant->leaf);
-}
-
-
-/* add an occupant to a bsp tree */
-void bsp_add_occupant(bsp_t *bsp, bsp_occupant_t *occupant, v3f_t *position)
-{
- _bsp_add_occupant(bsp, occupant, position, NULL);
-}
-
-
-/* Delete an occupant from a bsp tree.
- * Set reservation to prevent potentially freeing a node made empty by our delete that
- * we have a reference to (i.e. a cached lookup result, see bsp_move_occupant()).
- */
-static inline void _bsp_delete_occupant(bsp_t *bsp, bsp_occupant_t *occupant, bsp_node_t *reservation)
-{
- if (occupant->leaf->octrants) {
- fprintf(stderr, "BUG: deleting occupant(%p) from non-leaf bv(%p)\n", occupant, occupant->leaf);
- }
-
- /* delete the occupant */
- list_del(&occupant->occupants);
- occupant->leaf->n_occupants--;
-
- if (list_empty(&occupant->leaf->occupants)) {
- bsp_node_t *parent_bv;
-
- if (occupant->leaf->n_occupants) {
- fprintf(stderr, "BUG: bv_occupants empty but n_occupants=%u\n", occupant->leaf->n_occupants);
- }
-
- /* leaf is now empty, since nodes are allocated as clusters of 8, they aren't freed unless all nodes in the cluster are empty.
- * Determine if they're all empty, and free the parent's octrants as a set.
- * Repeat this process up the chain of parents, repeatedly converting empty parents into leaf nodes.
- * TODO: maybe just use the chunker instead?
- */
-
- for (parent_bv = occupant->leaf->parent; parent_bv && parent_bv != reservation; parent_bv = parent_bv->parent) {
- int i;
-
- /* are _all_ the parent's octrants freeable? */
- for (i = 0; i < 8; i++) {
- if (&parent_bv->octrants[i] == reservation ||
- parent_bv->octrants[i].octrants ||
- !list_empty(&parent_bv->octrants[i].occupants)) {
- goto _out;
- }
- }
-
- /* "freeing" really just entails putting the octrants cluster of nodes onto the free list */
- list_add(&parent_bv->octrants[0].occupants, &bsp->free);
- parent_bv->octrants = NULL;
- bsp_invalidate_lookup_cache(bsp);
- }
- }
-
-_out:
- occupant->leaf = NULL;
-}
-
-
-/* Delete an occupant from a bsp tree. */
-void bsp_delete_occupant(bsp_t *bsp, bsp_occupant_t *occupant)
-{
- _bsp_delete_occupant(bsp, occupant, NULL);
-}
-
-
-/* Move an occupant within a bsp tree to a new position */
-void bsp_move_occupant(bsp_t *bsp, bsp_occupant_t *occupant, v3f_t *position)
-{
- bsp_lookup_t lookup_res;
-
- if (v3f_equal(occupant->position, position)) {
- return;
- }
-
- /* TODO: now that there's a cache maintained in bsp->lookup_cache as well,
- * this feels a bit vestigial, see about consolidating things. We still
- * need to be able to pin lookup_res.bv in the delete, but why not just use
- * the one in bsp->lookup_cache.bv then stop having lookup_position return
- * a result at all???? this bsp isn't concurrent/threaded, so it doens't
- * really matter.
- */
- bsp_lookup_position(bsp, &bsp->root, occupant->position, &lookup_res);
- if (lookup_res.bv == occupant->leaf) {
- /* leaf unchanged, do nothing past lookup. */
- occupant->position = position;
- return;
- }
-
- _bsp_delete_occupant(bsp, occupant, lookup_res.bv);
- _bsp_add_occupant(bsp, occupant, position, &lookup_res);
-}
-
-
-static inline float square(float v)
-{
- return v * v;
-}
-
-
-typedef enum overlaps_t {
- OVERLAPS_NONE, /* objects are completely separated */
- OVERLAPS_PARTIALLY, /* objects surfaces one another */
- OVERLAPS_A_IN_B, /* first object is fully within the second */
- OVERLAPS_B_IN_A, /* second object is fully within the first */
-} overlaps_t;
-
-
-/* Returns wether the axis-aligned bounding box (AABB) overlaps the sphere.
- * Absolute vs. partial overlaps are distinguished, since it's an important optimization
- * to know if the sphere falls entirely within one partition of the octree.
- */
-static inline overlaps_t aabb_overlaps_sphere(v3f_t *aabb_min, v3f_t *aabb_max, v3f_t *sphere_center, float sphere_radius)
-{
- /* This implementation is based on James Arvo's from Graphics Gems pg. 335 */
- float r2 = square(sphere_radius);
- float dface = INFINITY;
- float dmin = 0;
- float dmax = 0;
- float a, b;
-
-#define per_dimension(_center, _box_max, _box_min) \
- a = square(_center - _box_min); \
- b = square(_center - _box_max); \
- \
- dmax += MAX(a, b); \
- if (_center >= _box_min && _center <= _box_max) { \
- /* sphere center within box */ \
- dface = MIN(dface, MIN(a, b)); \
- } else { \
- /* sphere center outside the box */ \
- dface = 0; \
- dmin += MIN(a, b); \
- }
-
- per_dimension(sphere_center->x, aabb_max->x, aabb_min->x);
- per_dimension(sphere_center->y, aabb_max->y, aabb_min->y);
- per_dimension(sphere_center->z, aabb_max->z, aabb_min->z);
-
- if (dmax < r2) {
- /* maximum distance to box smaller than radius, box is inside
- * the sphere */
- return OVERLAPS_A_IN_B;
- }
-
- if (dface > r2) {
- /* sphere center is within box (non-zero dface), and dface is
- * greater than sphere diameter, sphere is inside the box. */
- return OVERLAPS_B_IN_A;
- }
-
- if (dmin <= r2) {
- /* minimum distance from sphere center to box is smaller than
- * sphere's radius, surfaces intersect */
- return OVERLAPS_PARTIALLY;
- }
-
- return OVERLAPS_NONE;
-}
-
-
-typedef struct bsp_search_sphere_t {
- v3f_t *center;
- float radius_min;
- float radius_max;
- void (*cb)(bsp_t *, list_head_t *, void *);
- void *cb_data;
-} bsp_search_sphere_t;
-
-
-static overlaps_t _bsp_search_sphere(bsp_t *bsp, bsp_node_t *node, bsp_search_sphere_t *search, v3f_t *aabb_min, v3f_t *aabb_max)
-{
- overlaps_t res;
- v3f_t oaabb_min, oaabb_max;
-
- /* if the radius_max search doesn't overlap aabb_min:aabb_max at all, simply return. */
- res = aabb_overlaps_sphere(aabb_min, aabb_max, search->center, search->radius_max);
- if (res == OVERLAPS_NONE) {
- return res;
- }
-
- /* if the radius_max absolutely overlaps the AABB, we must see if the AABB falls entirely within radius_min so we can skip it. */
- if (res == OVERLAPS_A_IN_B) {
- res = aabb_overlaps_sphere(aabb_min, aabb_max, search->center, search->radius_min);
- if (res == OVERLAPS_A_IN_B) {
- /* AABB is entirely within radius_min, skip it. */
- return OVERLAPS_NONE;
- }
-
- if (res == OVERLAPS_NONE) {
- /* radius_min didn't overlap, radius_max overlapped aabb 100%, it's entirely within the range. */
- res = OVERLAPS_A_IN_B;
- } else {
- /* radius_min overlapped partially.. */
- res = OVERLAPS_PARTIALLY;
- }
- }
-
- /* if node is a leaf, call search->cb with the occupants, then return. */
- if (!node->octrants) {
- search->cb(bsp, &node->occupants, search->cb_data);
- return res;
- }
-
- /* node is a parent, recur on each octrant with appropriately adjusted aabb_min:aabb_max values */
- /* if any of the octrants absolutely overlaps the search sphere, skip the others by returning. */
-#define search_octrant(_oid, _aabb_min, _aabb_max) \
- res = _bsp_search_sphere(bsp, &node->octrants[_oid], search, _aabb_min, _aabb_max); \
- if (res == OVERLAPS_B_IN_A) { \
- return res; \
- }
-
- /* OCT_XL_YL_ZL and OCT_XR_YR_ZR AABBs don't require tedious composition */
- search_octrant(OCT_XL_YL_ZL, aabb_min, &node->center);
- search_octrant(OCT_XR_YR_ZR, &node->center, aabb_max);
-
- /* the rest are stitched together requiring temp storage and tedium */
- v3f_set(&oaabb_min, node->center.x, aabb_min->y, aabb_min->z);
- v3f_set(&oaabb_max, aabb_max->x, node->center.y, node->center.z);
- search_octrant(OCT_XR_YL_ZL, &oaabb_min, &oaabb_max);
-
- v3f_set(&oaabb_min, aabb_min->x, node->center.y, aabb_min->z);
- v3f_set(&oaabb_max, node->center.x, aabb_max->y, node->center.z);
- search_octrant(OCT_XL_YR_ZL, &oaabb_min, &oaabb_max);
-
- v3f_set(&oaabb_min, node->center.x, node->center.y, aabb_min->z);
- v3f_set(&oaabb_max, aabb_max->x, aabb_max->y, node->center.z);
- search_octrant(OCT_XR_YR_ZL, &oaabb_min, &oaabb_max);
-
- v3f_set(&oaabb_min, aabb_min->x, aabb_min->y, node->center.z);
- v3f_set(&oaabb_max, node->center.x, node->center.y, aabb_max->z);
- search_octrant(OCT_XL_YL_ZR, &oaabb_min, &oaabb_max);
-
- v3f_set(&oaabb_min, node->center.x, aabb_min->y, node->center.z);
- v3f_set(&oaabb_max, aabb_max->x, node->center.y, aabb_max->z);
- search_octrant(OCT_XR_YL_ZR, &oaabb_min, &oaabb_max);
-
- v3f_set(&oaabb_min, aabb_min->x, node->center.y, node->center.z);
- v3f_set(&oaabb_max, node->center.x, aabb_max->y, aabb_max->z);
- search_octrant(OCT_XL_YR_ZR, &oaabb_min, &oaabb_max);
-
-#undef search_octrant
-
- /* since early on an OVERLAPS_NONE short-circuits the function, and
- * OVERLAPS_ABSOLUTE also causes short-circuits, if we arrive here it's
- * a partial overlap
- */
- return OVERLAPS_PARTIALLY;
-}
-
-
-/* search the bsp tree for leaf nodes which intersect the space between radius_min and radius_max of a sphere @ center */
-/* for every leaf node found to intersect the sphere, cb is called with the leaf node's occupants list head */
-/* the callback cb must then further filter the occupants as necessary. */
-void bsp_search_sphere(bsp_t *bsp, v3f_t *center, float radius_min, float radius_max, void (*cb)(bsp_t *, list_head_t *, void *), void *cb_data)
-{
- bsp_search_sphere_t search = {
- .center = center,
- .radius_min = radius_min,
- .radius_max = radius_max,
- .cb = cb,
- .cb_data = cb_data,
- };
- v3f_t aabb_min = v3f_init(-1.0f, -1.0f, -1.0f);
- v3f_t aabb_max = v3f_init(1.0f, 1.0f, 1.0f);
-
- _bsp_search_sphere(bsp, &bsp->root, &search, &aabb_min, &aabb_max);
-}
© All Rights Reserved