29#elif defined(__SSE4_1__)
31#elif defined(__SSE2__)
35#ifdef HELIOS_CUDA_AVAILABLE
36#include <cuda_runtime.h>
39using namespace helios;
41#ifdef HELIOS_CUDA_AVAILABLE
52void launchBVHTraversal(
void *h_nodes,
int node_count,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_query_aabb_min,
float *h_query_aabb_max,
int num_queries,
53 unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_query);
54bool launchVoxelRayPathLengths(
int num_rays,
float *h_ray_origins,
float *h_ray_directions,
float grid_center_x,
float grid_center_y,
float grid_center_z,
float grid_size_x,
float grid_size_y,
float grid_size_z,
int grid_divisions_x,
55 int grid_divisions_y,
int grid_divisions_z,
int primitive_count,
int *h_voxel_ray_counts,
float *h_voxel_path_lengths,
int *h_voxel_transmitted,
int *h_voxel_hit_before,
int *h_voxel_hit_after,
int *h_voxel_hit_inside);
57void launchWarpEfficientBVH(
void *h_bvh_soa_gpu,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_ray_origins,
float *h_ray_directions,
float *h_ray_max_distances,
58 int num_rays,
unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_ray);
63 return make_float3(v.
x, v.
y, v.
z);
69 if (a_context ==
nullptr) {
76#ifdef HELIOS_CUDA_AVAILABLE
77 gpu_acceleration_enabled =
true;
79 gpu_acceleration_enabled =
false;
83 d_bvh_nodes =
nullptr;
84 d_primitive_indices =
nullptr;
85 gpu_memory_allocated =
false;
90 automatic_bvh_rebuilds =
true;
93 hierarchical_bvh_enabled =
false;
94 static_bvh_valid =
false;
97 tree_based_bvh_enabled =
false;
98 tree_isolation_distance = 5.0f;
99 obstacle_spatial_grid_initialized =
false;
103 static bool openmp_warning_issued =
false;
104 if (printmessages && !openmp_warning_issued) {
105 std::cout <<
"WARNING (CollisionDetection): OpenMP not available. Using serial CPU implementation. "
106 <<
"Performance will be significantly slower. Consider installing OpenMP for parallel execution." << std::endl;
107 openmp_warning_issued =
true;
117 voxel_data_initialized =
false;
121 use_flat_arrays =
false;
124 max_collision_distance = 10.0f;
128#ifdef HELIOS_CUDA_AVAILABLE
134 return findCollisions(std::vector<uint>{UUID}, allow_spatial_culling);
143 warnings.
addWarning(
"no_uuids_provided",
"No UUIDs provided");
144 warnings.
report(std::cerr);
149 std::vector<uint> valid_UUIDs;
150 for (
uint uuid: UUIDs) {
152 valid_UUIDs.push_back(uuid);
154 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Invalid UUID " + std::to_string(uuid) +
" provided");
161 std::vector<uint> all_collisions;
163 for (
uint UUID: valid_UUIDs) {
169 vec3 aabb_min, aabb_max;
172 std::vector<uint> collisions;
174#ifdef HELIOS_CUDA_AVAILABLE
175 if (gpu_acceleration_enabled && gpu_memory_allocated) {
176 collisions = traverseBVH_GPU(aabb_min, aabb_max);
178 collisions = traverseBVH_CPU(aabb_min, aabb_max);
181 collisions = traverseBVH_CPU(aabb_min, aabb_max);
185 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
188 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
192 std::sort(all_collisions.begin(), all_collisions.end());
193 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
195 return all_collisions;
203 if (primitive_UUIDs.empty() && object_IDs.empty()) {
204 warnings.
addWarning(
"no_inputs_provided",
"No UUIDs or object IDs provided");
205 warnings.
report(std::cerr);
210 std::vector<uint> all_test_UUIDs = primitive_UUIDs;
212 for (
uint ObjID: object_IDs) {
214 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Object ID " + std::to_string(ObjID) +
" does not exist");
218 all_test_UUIDs.insert(all_test_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
224std::vector<uint>
CollisionDetection::findCollisions(
const std::vector<uint> &query_UUIDs,
const std::vector<uint> &query_object_IDs,
const std::vector<uint> &target_UUIDs,
const std::vector<uint> &target_object_IDs,
bool allow_spatial_culling) {
229 if (query_UUIDs.empty() && query_object_IDs.empty()) {
230 warnings.
addWarning(
"no_query_inputs",
"No query UUIDs or object IDs provided");
231 warnings.
report(std::cerr);
236 std::vector<uint> all_query_UUIDs = query_UUIDs;
238 for (
uint ObjID: query_object_IDs) {
240 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Query object ID " + std::to_string(ObjID) +
" does not exist");
244 all_query_UUIDs.insert(all_query_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
248 if (!validateUUIDs(all_query_UUIDs)) {
249 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid query UUIDs provided");
254 if (target_UUIDs.empty() && target_object_IDs.empty()) {
259 std::vector<uint> all_target_UUIDs = target_UUIDs;
261 for (
uint ObjID: target_object_IDs) {
263 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Target object ID " + std::to_string(ObjID) +
" does not exist");
267 all_target_UUIDs.insert(all_target_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
271 if (tree_based_bvh_enabled && allow_spatial_culling && !all_query_UUIDs.empty()) {
274 if (!all_query_UUIDs.empty()) {
278 query_center = (min_corner + max_corner) * 0.5f;
284 all_target_UUIDs = effective_targets;
288 if (!all_target_UUIDs.empty() && !validateUUIDs(all_target_UUIDs)) {
289 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid target UUIDs provided");
293 if (!all_target_UUIDs.empty()) {
299 std::vector<uint> all_collisions;
301 for (
uint UUID: all_query_UUIDs) {
307 vec3 aabb_min, aabb_max;
310 std::vector<uint> collisions;
312#ifdef HELIOS_CUDA_AVAILABLE
313 if (gpu_acceleration_enabled && gpu_memory_allocated) {
314 collisions = traverseBVH_GPU(aabb_min, aabb_max);
316 collisions = traverseBVH_CPU(aabb_min, aabb_max);
319 collisions = traverseBVH_CPU(aabb_min, aabb_max);
323 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
326 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
330 std::sort(all_collisions.begin(), all_collisions.end());
331 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
333 warnings.
report(std::cerr);
334 return all_collisions;
343 std::vector<uint> primitives_to_include;
349 primitives_to_include = UUIDs;
353 if (primitives_to_include.empty()) {
354 warnings.
addWarning(
"no_primitives_for_bvh",
"No primitives found to build BVH");
355 warnings.
report(std::cerr);
360 std::vector<uint> valid_primitives;
361 if (!UUIDs.empty()) {
363 for (
uint uuid: primitives_to_include) {
365 valid_primitives.push_back(uuid);
367 helios_runtime_error(
"ERROR (CollisionDetection::buildBVH): Invalid UUID " + std::to_string(uuid) +
" provided");
372 for (
uint uuid: primitives_to_include) {
374 valid_primitives.push_back(uuid);
376 warnings.
addWarning(
"invalid_uuid_skipped",
"Skipping invalid UUID " + std::to_string(uuid));
380 if (valid_primitives.empty()) {
381 warnings.
addWarning(
"no_valid_primitives_after_filtering",
"No valid primitives found after filtering");
382 warnings.
report(std::cerr);
387 primitives_to_include = valid_primitives;
390 std::set<uint> new_primitive_set(primitives_to_include.begin(), primitives_to_include.end());
391 std::set<uint> old_primitive_set(primitive_indices.begin(), primitive_indices.end());
393 bool primitive_set_changed = (new_primitive_set != old_primitive_set);
395 if (primitive_set_changed) {
397 primitive_cache.clear();
402 primitive_indices.clear();
405 primitive_indices = primitives_to_include;
409 size_t max_nodes = std::max(
size_t(1), 2 * primitives_to_include.size());
411 bvh_nodes.resize(max_nodes);
412 next_available_node_index = 1;
416 std::unordered_set<uint> current_primitives(primitives_to_include.begin(), primitives_to_include.end());
419 auto cache_it = primitive_aabbs_cache.begin();
420 while (cache_it != primitive_aabbs_cache.end()) {
421 if (current_primitives.find(cache_it->first) == current_primitives.end()) {
422 cache_it = primitive_aabbs_cache.erase(cache_it);
429 for (
uint UUID: primitives_to_include) {
435 bool needs_update = (primitive_aabbs_cache.find(UUID) == primitive_aabbs_cache.end()) || (dirty_primitive_cache.find(UUID) != dirty_primitive_cache.end());
438 vec3 aabb_min, aabb_max;
440 primitive_aabbs_cache[UUID] = {aabb_min, aabb_max};
441 dirty_primitive_cache.erase(UUID);
446 buildBVHRecursive(0, 0, primitive_indices.size(), 0);
449 bvh_nodes.resize(next_available_node_index);
453 bvh_nodes_soa.clear();
454 bvh_nodes_soa.node_count = 0;
457#ifdef HELIOS_CUDA_AVAILABLE
458 if (gpu_acceleration_enabled) {
469 last_processed_uuids.clear();
470 last_processed_uuids.insert(primitives_to_include.begin(), primitives_to_include.end());
472 last_processed_deleted_uuids.clear();
473 last_processed_deleted_uuids.insert(context_deleted_uuids.begin(), context_deleted_uuids.end());
476 last_bvh_geometry.clear();
477 last_bvh_geometry.insert(primitives_to_include.begin(), primitives_to_include.end());
482 warnings.
report(std::cerr);
491 automatic_bvh_rebuilds =
false;
495 automatic_bvh_rebuilds =
true;
499 hierarchical_bvh_enabled =
true;
500 static_bvh_valid =
false;
504 hierarchical_bvh_enabled =
false;
506 static_bvh_nodes.clear();
507 static_bvh_primitives.clear();
508 static_bvh_valid =
false;
509 last_static_bvh_geometry.clear();
512void CollisionDetection::updateHierarchicalBVH(
const std::set<uint> &requested_geometry,
bool force_rebuild) {
515 if (!static_bvh_valid || force_rebuild || static_geometry_cache != last_static_bvh_geometry) {
520 std::vector<uint> dynamic_geometry;
521 for (
uint uuid: requested_geometry) {
522 if (static_geometry_cache.find(uuid) == static_geometry_cache.end()) {
523 dynamic_geometry.push_back(uuid);
529 if (!dynamic_geometry.empty()) {
534 primitive_indices.clear();
538 last_bvh_geometry = requested_geometry;
544 if (static_geometry_cache.empty()) {
545 static_bvh_nodes.clear();
546 static_bvh_primitives.clear();
547 static_bvh_valid =
false;
551 std::vector<uint> static_primitives(static_geometry_cache.begin(), static_geometry_cache.end());
556 std::vector<BVHNode> temp_nodes;
557 std::vector<uint> temp_primitives;
560 std::swap(bvh_nodes, temp_nodes);
561 std::swap(primitive_indices, temp_primitives);
567 static_bvh_nodes = bvh_nodes;
568 static_bvh_primitives = primitive_indices;
569 std::swap(bvh_nodes, temp_nodes);
570 std::swap(primitive_indices, temp_primitives);
572 static_bvh_valid =
true;
573 last_static_bvh_geometry = static_geometry_cache;
578 std::set<uint> requested_geometry(UUIDs.begin(), UUIDs.end());
581 bool geometry_changed = (requested_geometry != last_bvh_geometry) || bvh_dirty;
583 if (!geometry_changed && !force_rebuild) {
588 if (hierarchical_bvh_enabled) {
589 updateHierarchicalBVH(requested_geometry, force_rebuild);
594 if (force_rebuild || bvh_nodes.empty()) {
599 std::set<uint> added_geometry, removed_geometry;
602 std::set_difference(requested_geometry.begin(), requested_geometry.end(), last_bvh_geometry.begin(), last_bvh_geometry.end(), std::inserter(added_geometry, added_geometry.begin()));
605 std::set_difference(last_bvh_geometry.begin(), last_bvh_geometry.end(), requested_geometry.begin(), requested_geometry.end(), std::inserter(removed_geometry, removed_geometry.begin()));
608 size_t total_change = added_geometry.size() + removed_geometry.size();
609 size_t current_size = std::max(last_bvh_geometry.size(), requested_geometry.size());
613 bool mostly_additions = (removed_geometry.size() < added_geometry.size() * 0.1f);
614 float change_threshold = mostly_additions ? 0.5f : 0.2f;
616 if (current_size == 0 || (
float(total_change) /
float(current_size)) > change_threshold) {
620 incrementalUpdateBVH(added_geometry, removed_geometry, requested_geometry);
625 last_bvh_geometry = requested_geometry;
631 static_geometry_cache.clear();
632 static_geometry_cache.insert(UUIDs.begin(), UUIDs.end());
635void CollisionDetection::ensureBVHCurrent() {
637 if (!automatic_bvh_rebuilds) {
642 if (bvh_nodes.empty()) {
644 std::cout <<
"Building initial BVH..." << std::endl;
652 std::vector<uint> context_dirty_uuids = context->
getDirtyUUIDs(
false);
656 std::set<uint> current_dirty(context_dirty_uuids.begin(), context_dirty_uuids.end());
657 std::set<uint> current_deleted(context_deleted_uuids.begin(), context_deleted_uuids.end());
660 bool has_new_dirty =
false;
661 bool has_new_deleted =
false;
664 for (
uint uuid: current_dirty) {
665 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
666 has_new_dirty =
true;
672 for (
uint uuid: current_deleted) {
673 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
674 has_new_deleted =
true;
680 if (has_new_dirty || has_new_deleted) {
682 std::cout <<
"Geometry has changed since last BVH build, rebuilding..." << std::endl;
693 if (bvh_nodes.empty()) {
698 std::vector<uint> all_context_uuids = context->
getAllUUIDs();
699 for (
uint uuid: all_context_uuids) {
700 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
707 for (
uint uuid: context_deleted_uuids) {
708 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
714 for (
uint uuid: primitive_indices) {
724#ifdef HELIOS_CUDA_AVAILABLE
725 gpu_acceleration_enabled =
true;
726 if (!bvh_nodes.empty()) {
731 std::cerr <<
"WARNING: GPU acceleration requested but CUDA not available. Ignoring request." << std::endl;
737 gpu_acceleration_enabled =
false;
738#ifdef HELIOS_CUDA_AVAILABLE
744 return gpu_acceleration_enabled;
748 printmessages =
false;
752 printmessages =
true;
756 return primitive_indices.size();
761 node_count = bvh_nodes.size();
766 std::function<void(
uint,
size_t)> traverse = [&](
uint node_idx,
size_t depth) {
767 if (node_idx >= bvh_nodes.size())
770 const BVHNode &node = bvh_nodes[node_idx];
771 max_depth = std::max(max_depth, depth);
776 if (node.left_child != 0xFFFFFFFF) {
777 traverse(node.left_child, depth + 1);
779 if (node.right_child != 0xFFFFFFFF) {
780 traverse(node.right_child, depth + 1);
785 if (!bvh_nodes.empty()) {
790void CollisionDetection::calculateAABB(
const std::vector<uint> &primitives,
vec3 &aabb_min,
vec3 &aabb_max)
const {
792 if (primitives.empty()) {
799 size_t first_valid = 0;
800 while (first_valid < primitives.size() && !context->
doesPrimitiveExist(primitives[first_valid])) {
803 if (first_valid >= primitives.size()) {
814 for (
size_t i = first_valid + 1; i < primitives.size(); i++) {
818 vec3 prim_min, prim_max;
821 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
822 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
823 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
825 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
826 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
827 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
831void CollisionDetection::buildBVHRecursive(
uint node_index,
size_t primitive_start,
size_t primitive_count,
int depth) {
834 if (node_index >= bvh_nodes.size()) {
835 throw std::runtime_error(
"CollisionDetection: BVH recursive access exceeded pre-allocated capacity");
839 if (primitive_start + primitive_count > primitive_indices.size()) {
840 throw std::runtime_error(
"CollisionDetection: BVH primitive bounds check failed - primitive_start(" + std::to_string(primitive_start) +
") + primitive_count(" + std::to_string(primitive_count) +
") > primitive_indices.size(" +
841 std::to_string(primitive_indices.size()) +
")");
844 BVHNode &node = bvh_nodes[node_index];
847 if (primitive_count == 0) {
852 uint first_uuid = primitive_indices[primitive_start];
853 auto it = primitive_aabbs_cache.find(first_uuid);
854 if (it == primitive_aabbs_cache.end()) {
860 const auto &first_cached_aabb = it->second;
861 node.aabb_min = first_cached_aabb.first;
862 node.aabb_max = first_cached_aabb.second;
865 for (
size_t i = 1; i < primitive_count; i++) {
866 uint uuid = primitive_indices[primitive_start + i];
867 auto it = primitive_aabbs_cache.find(uuid);
868 if (it == primitive_aabbs_cache.end()) {
871 const auto &cached_aabb = it->second;
872 node.aabb_min.x = std::min(node.aabb_min.x, cached_aabb.first.x);
873 node.aabb_min.y = std::min(node.aabb_min.y, cached_aabb.first.y);
874 node.aabb_min.z = std::min(node.aabb_min.z, cached_aabb.first.z);
876 node.aabb_max.x = std::max(node.aabb_max.x, cached_aabb.second.x);
877 node.aabb_max.y = std::max(node.aabb_max.y, cached_aabb.second.y);
878 node.aabb_max.z = std::max(node.aabb_max.z, cached_aabb.second.z);
884 const int MAX_PRIMITIVES_PER_LEAF = (primitive_indices.size() > 500000) ? 500 : 100;
885 const int MAX_DEPTH = (primitive_indices.size() > 500000) ? 6 : 10;
887 if (primitive_count <= MAX_PRIMITIVES_PER_LEAF || depth >= MAX_DEPTH) {
890 node.primitive_start = primitive_start;
891 node.primitive_count = primitive_count;
892 node.left_child = 0xFFFFFFFF;
893 node.right_child = 0xFFFFFFFF;
898 vec3 extent = node.aabb_max - node.aabb_min;
900 if (extent.
y > extent.
x)
902 if (extent.
z > (split_axis == 0 ? extent.
x : extent.y))
907 std::sort(primitive_indices.begin() + primitive_start, primitive_indices.begin() + primitive_start + primitive_count, [&](
uint a,
uint b) {
909 auto it_a = primitive_aabbs_cache.find(a);
910 auto it_b = primitive_aabbs_cache.find(b);
911 if (it_a == primitive_aabbs_cache.end() || it_b == primitive_aabbs_cache.end()) {
915 const auto &aabb_a = it_a->second;
916 const auto &aabb_b = it_b->second;
919 float centroid_a_coord, centroid_b_coord;
920 if (split_axis == 0) {
921 centroid_a_coord = 0.5f * (aabb_a.first.x + aabb_a.second.x);
922 centroid_b_coord = 0.5f * (aabb_b.first.x + aabb_b.second.x);
923 }
else if (split_axis == 1) {
924 centroid_a_coord = 0.5f * (aabb_a.first.y + aabb_a.second.y);
925 centroid_b_coord = 0.5f * (aabb_b.first.y + aabb_b.second.y);
927 centroid_a_coord = 0.5f * (aabb_a.first.z + aabb_a.second.z);
928 centroid_b_coord = 0.5f * (aabb_b.first.z + aabb_b.second.z);
933 if (centroid_a_coord == centroid_b_coord) {
936 return centroid_a_coord < centroid_b_coord;
940 size_t split_index = primitive_count / 2;
943 uint left_child_index = next_available_node_index++;
944 uint right_child_index = next_available_node_index++;
947 if (right_child_index >= bvh_nodes.size()) {
948 throw std::runtime_error(
"CollisionDetection: BVH node allocation exceeded pre-calculated capacity");
952 BVHNode &updated_node = bvh_nodes[node_index];
953 updated_node.left_child = left_child_index;
954 updated_node.right_child = right_child_index;
955 updated_node.is_leaf =
false;
956 updated_node.primitive_start = 0;
957 updated_node.primitive_count = 0;
960 buildBVHRecursive(left_child_index, primitive_start, split_index, depth + 1);
961 buildBVHRecursive(right_child_index, primitive_start + split_index, primitive_count - split_index, depth + 1);
964std::vector<uint> CollisionDetection::traverseBVH_CPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
966 std::vector<uint> results;
968 if (bvh_nodes.empty()) {
973 std::vector<uint> node_stack;
974 node_stack.push_back(0);
976 while (!node_stack.empty()) {
977 uint node_idx = node_stack.back();
978 node_stack.pop_back();
980 if (node_idx >= bvh_nodes.size())
983 const BVHNode &node = bvh_nodes[node_idx];
986 if (!aabbIntersect(query_aabb_min, query_aabb_max, node.aabb_min, node.aabb_max)) {
992 for (
uint i = 0; i < node.primitive_count; i++) {
993 uint primitive_id = primitive_indices[node.primitive_start + i];
999 vec3 prim_min, prim_max;
1003 if (aabbIntersect(query_aabb_min, query_aabb_max, prim_min, prim_max)) {
1004 results.push_back(primitive_id);
1009 if (node.left_child != 0xFFFFFFFF) {
1010 node_stack.push_back(node.left_child);
1012 if (node.right_child != 0xFFFFFFFF) {
1013 node_stack.push_back(node.right_child);
1021#ifdef HELIOS_CUDA_AVAILABLE
1022std::vector<uint> CollisionDetection::traverseBVH_GPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
1023 if (!gpu_memory_allocated) {
1024 helios_runtime_error(
"ERROR: GPU traversal requested but GPU memory is not allocated. Call buildBVH() or transferBVHToGPU() first.");
1028 float query_min_array[3] = {query_aabb_min.
x, query_aabb_min.
y, query_aabb_min.
z};
1029 float query_max_array[3] = {query_aabb_max.
x, query_aabb_max.
y, query_aabb_max.
z};
1032 std::vector<float> primitive_min_array(primitive_indices.size() * 3);
1033 std::vector<float> primitive_max_array(primitive_indices.size() * 3);
1035 for (
size_t i = 0; i < primitive_indices.size(); i++) {
1036 uint uuid = primitive_indices[i];
1037 auto it = primitive_aabbs_cache.find(uuid);
1038 if (it == primitive_aabbs_cache.end()) {
1041 const auto &cached_aabb = it->second;
1043 primitive_min_array[i * 3] = cached_aabb.first.x;
1044 primitive_min_array[i * 3 + 1] = cached_aabb.first.y;
1045 primitive_min_array[i * 3 + 2] = cached_aabb.first.z;
1047 primitive_max_array[i * 3] = cached_aabb.second.x;
1048 primitive_max_array[i * 3 + 1] = cached_aabb.second.y;
1049 primitive_max_array[i * 3 + 2] = cached_aabb.second.z;
1052 const int max_results = 1000;
1053 std::vector<unsigned int> results(max_results);
1054 unsigned int result_count = 0;
1057 launchBVHTraversal(d_bvh_nodes, bvh_nodes.size(), d_primitive_indices, primitive_indices.size(), primitive_min_array.data(), primitive_max_array.data(), query_min_array, query_max_array, 1, results.data(), &result_count, max_results);
1060 std::vector<uint> final_results;
1061 for (
unsigned int i = 0; i < result_count; i++) {
1062 final_results.push_back(results[i]);
1065 return final_results;
1069bool CollisionDetection::aabbIntersect(
const vec3 &min1,
const vec3 &max1,
const vec3 &min2,
const vec3 &max2) {
1070 return (min1.
x <= max2.
x && max1.
x >= min2.
x) && (min1.
y <= max2.
y && max1.
y >= min2.
y) && (min1.
z <= max2.
z && max1.
z >= min2.
z);
1073bool CollisionDetection::rayAABBIntersect(
const vec3 &origin,
const vec3 &direction,
const vec3 &aabb_min,
const vec3 &aabb_max,
float &t_min,
float &t_max)
const {
1076 t_max = std::numeric_limits<float>::max();
1079 for (
int i = 0; i < 3; i++) {
1080 float dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
1081 float orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
1082 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1083 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1085 if (std::abs(dir_component) < 1e-9f) {
1087 if (orig_component < min_component || orig_component > max_component) {
1092 float t1 = (min_component - orig_component) / dir_component;
1093 float t2 = (max_component - orig_component) / dir_component;
1101 t_min = std::max(t_min, t1);
1102 t_max = std::min(t_max, t2);
1105 if (t_min > t_max) {
1112 return t_max >= 0.0f;
1115bool CollisionDetection::coneAABBIntersect(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1117 if (cone.apex.x >= aabb_min.
x && cone.apex.x <= aabb_max.
x && cone.apex.y >= aabb_min.
y && cone.apex.y <= aabb_max.
y && cone.apex.z >= aabb_min.
z && cone.apex.z <= aabb_max.
z) {
1122 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1123 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1124 float box_radius = box_half_extents.
magnitude();
1127 if (cone.height <= 0.0f) {
1129 for (
int i = 0; i < 8; i++) {
1130 vec3 corner =
make_vec3((i & 1) ? aabb_max.
x : aabb_min.x, (i & 2) ? aabb_max.y : aabb_min.y, (i & 4) ? aabb_max.z : aabb_min.z);
1133 vec3 apex_to_corner = corner - cone.apex;
1134 float distance_along_axis = apex_to_corner * cone.axis;
1137 if (distance_along_axis > 0) {
1139 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1140 if (cos_angle >= cosf(cone.half_angle)) {
1149 float t_max = std::numeric_limits<float>::max();
1151 for (
int i = 0; i < 3; i++) {
1152 float axis_component = (i == 0) ? cone.axis.x : (i == 1) ? cone.axis.y : cone.axis.z;
1153 float apex_component = (i == 0) ? cone.apex.x : (i == 1) ? cone.apex.y : cone.apex.z;
1154 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1155 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1157 if (std::abs(axis_component) < 1e-6f) {
1159 if (apex_component < min_component || apex_component > max_component) {
1164 float t1 = (min_component - apex_component) / axis_component;
1165 float t2 = (max_component - apex_component) / axis_component;
1170 t_min = std::max(t_min, t1);
1171 t_max = std::min(t_max, t2);
1173 if (t_min > t_max) {
1181 if (t_min >= 0 && t_max >= 0) {
1184 float t_check = std::max(0.0f, t_min);
1185 vec3 axis_point = cone.apex + cone.axis * t_check;
1188 vec3 closest_in_box =
make_vec3(std::max(aabb_min.
x, std::min(axis_point.
x, aabb_max.
x)), std::max(aabb_min.
y, std::min(axis_point.
y, aabb_max.
y)), std::max(aabb_min.
z, std::min(axis_point.
z, aabb_max.
z)));
1191 vec3 apex_to_point = closest_in_box - cone.apex;
1192 float distance_along_axis = apex_to_point * cone.axis;
1194 if (distance_along_axis > 0) {
1195 float distance_to_point = apex_to_point.
magnitude();
1196 if (distance_to_point > 0) {
1197 float cos_angle = distance_along_axis / distance_to_point;
1198 if (cos_angle >= cosf(cone.half_angle)) {
1207 for (
int i = 0; i < 8; i++) {
1208 vec3 corner =
make_vec3((i & 1) ? aabb_max.
x : aabb_min.x, (i & 2) ? aabb_max.y : aabb_min.y, (i & 4) ? aabb_max.z : aabb_min.z);
1211 vec3 apex_to_corner = corner - cone.apex;
1212 float distance_along_axis = apex_to_corner * cone.axis;
1215 if (distance_along_axis > 0 && distance_along_axis <= cone.height) {
1217 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1218 if (cos_angle >= cosf(cone.half_angle)) {
1225 vec3 base_center = cone.apex + cone.axis * cone.height;
1226 float base_radius = cone.height * tanf(cone.half_angle);
1229 vec3 closest_point =
make_vec3(std::max(aabb_min.
x, std::min(base_center.
x, aabb_max.
x)), std::max(aabb_min.
y, std::min(base_center.
y, aabb_max.
y)), std::max(aabb_min.
z, std::min(base_center.
z, aabb_max.
z)));
1231 float dist_sq = (closest_point - base_center).magnitude();
1232 if (dist_sq <= base_radius) {
1242bool CollisionDetection::coneAABBIntersectFast(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1247 if (cone.apex.x >= aabb_min.
x && cone.apex.x <= aabb_max.
x && cone.apex.y >= aabb_min.
y && cone.apex.y <= aabb_max.
y && cone.apex.z >= aabb_min.
z && cone.apex.z <= aabb_max.
z) {
1252 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1253 vec3 apex_to_center = box_center - cone.apex;
1254 float distance_along_axis = apex_to_center * cone.axis;
1256 if (distance_along_axis <= 0.0f) {
1261 if (cone.height > 0.0f && distance_along_axis > cone.height) {
1263 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1264 float box_radius = box_half_extents.
magnitude();
1265 if (distance_along_axis - box_radius > cone.height) {
1271 float max_distance = (cone.height > 0.0f) ? cone.height : distance_along_axis;
1272 float max_radius_at_distance = max_distance * tanf(cone.half_angle);
1275 vec3 axis_point = cone.apex + cone.axis * distance_along_axis;
1276 float distance_from_axis = (box_center - axis_point).magnitude();
1279 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1280 float box_radius = box_half_extents.
magnitude();
1282 if (distance_from_axis > max_radius_at_distance + box_radius) {
1288 return coneAABBIntersect(cone, aabb_min, aabb_max);
1293int CollisionDetection::calculateOptimalBinCount(
float cone_half_angle,
int geometry_count) {
1295 float base_resolution =
M_PI / 180.0f;
1296 float cone_solid_angle = 2.0f *
M_PI * (1.0f - cosf(cone_half_angle));
1297 int optimal_bins = (int) (cone_solid_angle / (base_resolution * base_resolution));
1301 int max_bins = std::min(1024, geometry_count * 4);
1303 return std::clamp(optimal_bins, min_bins, max_bins);
1306std::vector<uint> CollisionDetection::filterPrimitivesParallel(
const Cone &cone,
const std::vector<uint> &primitive_uuids) {
1307 std::vector<uint> filtered_uuids;
1309 if (primitive_uuids.empty()) {
1310 return filtered_uuids;
1314 filtered_uuids.reserve(primitive_uuids.size() / 10);
1318 const int num_threads = omp_get_max_threads();
1319 std::vector<std::vector<uint>> thread_results(num_threads);
1322 for (
int i = 0; i < num_threads; i++) {
1323 thread_results[i].reserve(primitive_uuids.size() / (num_threads * 10));
1329 int thread_id = omp_get_thread_num();
1330 std::vector<uint> &local_results = thread_results[thread_id];
1332#pragma omp for nowait
1333 for (
int i = 0; i < static_cast<int>(primitive_uuids.size()); i++) {
1334 uint uuid = primitive_uuids[i];
1339 if (!vertices.empty()) {
1341 vec3 aabb_min = vertices[0];
1342 vec3 aabb_max = vertices[0];
1343 for (
const vec3 &vertex: vertices) {
1344 aabb_min =
make_vec3(std::min(aabb_min.
x, vertex.x), std::min(aabb_min.
y, vertex.y), std::min(aabb_min.
z, vertex.z));
1345 aabb_max =
make_vec3(std::max(aabb_max.
x, vertex.x), std::max(aabb_max.
y, vertex.y), std::max(aabb_max.
z, vertex.z));
1349 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1350 local_results.push_back(uuid);
1358 size_t total_count = 0;
1359 for (
const auto &thread_result: thread_results) {
1360 total_count += thread_result.size();
1363 filtered_uuids.reserve(total_count);
1364 for (
const auto &thread_result: thread_results) {
1365 filtered_uuids.insert(filtered_uuids.end(), thread_result.begin(), thread_result.end());
1369 for (
size_t i = 0; i < primitive_uuids.size(); i++) {
1370 uint uuid = primitive_uuids[i];
1375 if (!vertices.empty()) {
1377 vec3 aabb_min = vertices[0];
1378 vec3 aabb_max = vertices[0];
1379 for (
const vec3 &vertex: vertices) {
1380 aabb_min =
make_vec3(std::min(aabb_min.
x, vertex.x), std::min(aabb_min.
y, vertex.y), std::min(aabb_min.
z, vertex.z));
1381 aabb_max =
make_vec3(std::max(aabb_max.
x, vertex.x), std::max(aabb_max.
y, vertex.y), std::max(aabb_max.
z, vertex.z));
1385 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1386 filtered_uuids.push_back(uuid);
1393 return filtered_uuids;
1396float CollisionDetection::cartesianToSphericalCone(
const vec3 &vector,
const vec3 &cone_axis,
float &theta,
float &phi) {
1398 if (distance < 1e-6f) {
1404 vec3 normalized_vector = vector / distance;
1407 float cos_phi = normalized_vector * cone_axis;
1408 phi = acosf(std::clamp(cos_phi, -1.0f, 1.0f));
1418 vec3 projected = normalized_vector - cone_axis * cos_phi;
1421 float cos_theta = projected * right;
1422 float sin_theta = projected * forward;
1423 theta = atan2f(sin_theta, cos_theta);
1425 theta += 2.0f *
M_PI;
1433bool CollisionDetection::sphericalCoordsToBinIndices(
float theta,
float phi,
const AngularBins &bins,
int &theta_bin,
int &phi_bin) {
1435 if (phi < 0.0f || theta < 0.0f || theta >= 2.0f *
M_PI) {
1440 theta_bin = (int) (theta * bins.theta_divisions / (2.0f *
M_PI));
1441 phi_bin = (int) (phi * bins.phi_divisions /
M_PI);
1444 theta_bin = std::clamp(theta_bin, 0, bins.theta_divisions - 1);
1445 phi_bin = std::clamp(phi_bin, 0, bins.phi_divisions - 1);
1450void CollisionDetection::projectGeometryToBins(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1453 const int PARALLEL_THRESHOLD = 500;
1455 if (filtered_uuids.size() > PARALLEL_THRESHOLD) {
1458 const int num_threads = omp_get_max_threads();
1459 std::vector<AngularBins> thread_bins(num_threads, AngularBins(bins.theta_divisions, bins.phi_divisions));
1463 int thread_id = omp_get_thread_num();
1464 AngularBins &local_bins = thread_bins[thread_id];
1466#pragma omp for nowait
1467 for (
int i = 0; i < static_cast<int>(filtered_uuids.size()); i++) {
1468 uint uuid = filtered_uuids[i];
1474 for (
const vec3 &vertex: vertices) {
1475 vec3 apex_to_vertex = vertex - cone.apex;
1476 float distance = apex_to_vertex.
magnitude();
1478 if (distance > 1e-6f) {
1480 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1483 if (phi <= cone.half_angle) {
1484 int theta_bin, phi_bin;
1485 if (sphericalCoordsToBinIndices(theta, phi, local_bins, theta_bin, phi_bin)) {
1486 local_bins.setCovered(theta_bin, phi_bin, distance);
1496 for (
const auto &thread_bin: thread_bins) {
1497 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1498 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1499 if (thread_bin.isCovered(theta, phi)) {
1500 int index = theta * bins.phi_divisions + phi;
1501 float thread_depth = thread_bin.depth_values[index];
1502 bins.setCovered(theta, phi, thread_depth);
1509 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1513 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1517void CollisionDetection::projectGeometryToBinsSerial(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1518 for (
uint uuid: filtered_uuids) {
1523 for (
const vec3 &vertex: vertices) {
1524 vec3 apex_to_vertex = vertex - cone.apex;
1525 float distance = apex_to_vertex.
magnitude();
1527 if (distance > 1e-6f) {
1529 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1532 if (phi <= cone.half_angle) {
1533 int theta_bin, phi_bin;
1534 if (sphericalCoordsToBinIndices(theta, phi, bins, theta_bin, phi_bin)) {
1535 bins.setCovered(theta_bin, phi_bin, distance);
1544std::vector<CollisionDetection::Gap> CollisionDetection::findGapsInCoverageMap(
const AngularBins &bins,
const Cone &cone) {
1545 std::vector<Gap> gaps;
1548 std::vector<std::vector<bool>> visited(bins.theta_divisions, std::vector<bool>(bins.phi_divisions,
false));
1550 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1551 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1552 if (!bins.isCovered(theta, phi) && !visited[theta][phi]) {
1554 Gap gap = floodFillGap(bins, theta, phi, visited, cone);
1557 const float MIN_GAP_SIZE_STERADIANS = 0.01f;
1558 if (gap.angular_size > MIN_GAP_SIZE_STERADIANS) {
1559 gaps.push_back(gap);
1566 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.angular_size > b.angular_size; });
1571CollisionDetection::Gap CollisionDetection::floodFillGap(
const AngularBins &bins,
int start_theta,
int start_phi, std::vector<std::vector<bool>> &visited,
const Cone &cone) {
1573 std::queue<std::pair<int, int>> queue;
1574 queue.push({start_theta, start_phi});
1576 float total_solid_angle = 0;
1577 vec3 weighted_center(0, 0, 0);
1579 while (!queue.empty()) {
1580 auto [theta, phi] = queue.front();
1583 if (visited[theta][phi] || bins.isCovered(theta, phi))
1585 visited[theta][phi] =
true;
1588 float bin_solid_angle = calculateBinSolidAngle(theta, phi, bins, cone.half_angle);
1589 total_solid_angle += bin_solid_angle;
1592 vec3 bin_direction = binIndicesToCartesian(theta, phi, bins, cone);
1593 weighted_center = weighted_center + bin_direction * bin_solid_angle;
1596 addUnoccupiedNeighbors(theta, phi, bins, visited, queue);
1599 gap.angular_size = total_solid_angle;
1600 gap.center_direction = weighted_center.magnitude() > 1e-6f ? weighted_center.normalize() : cone.axis;
1605float CollisionDetection::calculateBinSolidAngle(
int theta_bin,
int phi_bin,
const AngularBins &bins,
float cone_half_angle) {
1607 float theta_step = 2.0f *
M_PI / bins.theta_divisions;
1608 float phi_step = cone_half_angle / bins.phi_divisions;
1611 float phi = (phi_bin + 0.5f) * phi_step;
1612 return theta_step * phi_step * sinf(phi);
1615vec3 CollisionDetection::binIndicesToCartesian(
int theta_bin,
int phi_bin,
const AngularBins &bins,
const Cone &cone) {
1617 float theta = (theta_bin + 0.5f) * 2.0f *
M_PI / bins.theta_divisions;
1618 float phi = (phi_bin + 0.5f) * cone.half_angle / bins.phi_divisions;
1627 float sin_phi = sinf(phi);
1628 float cos_phi = cosf(phi);
1629 float sin_theta = sinf(theta);
1630 float cos_theta = cosf(theta);
1632 vec3 direction = cone.axis * cos_phi + (right * cos_theta + forward * sin_theta) * sin_phi;
1636void CollisionDetection::addUnoccupiedNeighbors(
int theta,
int phi,
const AngularBins &bins, std::vector<std::vector<bool>> &visited, std::queue<std::pair<int, int>> &queue) {
1638 const int neighbors[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
1640 for (
int i = 0; i < 4; i++) {
1641 int new_theta = theta + neighbors[i][0];
1642 int new_phi = phi + neighbors[i][1];
1646 new_theta += bins.theta_divisions;
1647 if (new_theta >= bins.theta_divisions)
1648 new_theta -= bins.theta_divisions;
1651 if (new_phi >= 0 && new_phi < bins.phi_divisions) {
1652 if (!visited[new_theta][new_phi] && !bins.isCovered(new_theta, new_phi)) {
1653 queue.push({new_theta, new_phi});
1670 if (initialSamples <= 0 || half_angle <= 0.0f || half_angle >
M_PI) {
1671 if (printmessages) {
1672 std::cerr <<
"WARNING: Invalid parameters for findOptimalConePath" << std::endl;
1677 if (bvh_nodes.empty()) {
1684 std::vector<Gap> detected_gaps = detectGapsInCone(apex, centralAxis, half_angle, height, initialSamples);
1686 if (detected_gaps.empty()) {
1696 scoreGapsByFishEyeMetric(detected_gaps, centralAxis);
1699 result.
direction = findOptimalGapDirection(detected_gaps, centralAxis);
1702 float max_distance = (height > 0.0f) ? height : -1.0f;
1707 if (!detected_gaps.empty()) {
1709 const Gap &best_gap = detected_gaps[0];
1710 result.
confidence = std::min(1.0f, best_gap.angular_size * 10.0f);
1716#ifdef HELIOS_CUDA_AVAILABLE
1717void CollisionDetection::allocateGPUMemory() {
1718 if (gpu_memory_allocated) {
1722 if (bvh_nodes.empty() || primitive_indices.empty()) {
1727 d_bvh_nodes =
nullptr;
1728 d_primitive_indices =
nullptr;
1731 int deviceCount = 0;
1732 cudaError_t err = cudaGetDeviceCount(&deviceCount);
1733 if (err != cudaSuccess || deviceCount == 0) {
1735 if (printmessages) {
1736 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): CUDA runtime unavailable (" << cudaGetErrorString(err) <<
"). Falling back to CPU-only mode." << std::endl;
1738 gpu_acceleration_enabled =
false;
1743 size_t bvh_size = bvh_nodes.size() *
sizeof(
GPUBVHNode);
1744 size_t indices_size = primitive_indices.size() *
sizeof(
uint);
1747 if (bvh_size == 0 || indices_size == 0) {
1752 err = cudaMalloc(&d_bvh_nodes, bvh_size);
1753 if (err != cudaSuccess) {
1755 if (printmessages) {
1756 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): Failed to allocate GPU memory (" << cudaGetErrorString(err) <<
"). Falling back to CPU-only mode." << std::endl;
1758 gpu_acceleration_enabled =
false;
1763 err = cudaMalloc((
void **) &d_primitive_indices, indices_size);
1764 if (err != cudaSuccess) {
1766 cudaFree(d_bvh_nodes);
1767 d_bvh_nodes =
nullptr;
1768 if (printmessages) {
1769 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): Failed to allocate primitive indices (" << cudaGetErrorString(err) <<
"). Falling back to CPU-only mode." << std::endl;
1771 gpu_acceleration_enabled =
false;
1776 gpu_memory_allocated =
true;
1780#ifdef HELIOS_CUDA_AVAILABLE
1781void CollisionDetection::freeGPUMemory() {
1782 if (!gpu_memory_allocated)
1786 cudaFree(d_bvh_nodes);
1787 d_bvh_nodes =
nullptr;
1790 if (d_primitive_indices) {
1791 cudaFree(d_primitive_indices);
1792 d_primitive_indices =
nullptr;
1795 gpu_memory_allocated =
false;
1799#ifdef HELIOS_CUDA_AVAILABLE
1800void CollisionDetection::transferBVHToGPU() {
1801 if (!gpu_acceleration_enabled || bvh_nodes.empty()) {
1806 if (gpu_memory_allocated) {
1809 allocateGPUMemory();
1813 if (!gpu_acceleration_enabled) {
1818 if (!gpu_memory_allocated || d_bvh_nodes ==
nullptr || d_primitive_indices ==
nullptr) {
1823 std::vector<GPUBVHNode> gpu_nodes(bvh_nodes.size());
1824 for (
size_t i = 0; i < bvh_nodes.size(); i++) {
1825 const BVHNode &cpu_node = bvh_nodes[i];
1828 gpu_node.
aabb_min = heliosVecToFloat3(cpu_node.aabb_min);
1829 gpu_node.
aabb_max = heliosVecToFloat3(cpu_node.aabb_max);
1834 gpu_node.
is_leaf = cpu_node.is_leaf ? 1 : 0;
1839 cudaError_t err = cudaMemcpy(d_bvh_nodes, gpu_nodes.data(), gpu_nodes.size() *
sizeof(
GPUBVHNode), cudaMemcpyHostToDevice);
1840 if (err != cudaSuccess) {
1841 helios_runtime_error(
"CUDA error transferring BVH nodes: " + std::string(cudaGetErrorString(err)));
1844 err = cudaMemcpy(d_primitive_indices, primitive_indices.data(), primitive_indices.size() *
sizeof(
uint), cudaMemcpyHostToDevice);
1845 if (err != cudaSuccess) {
1846 helios_runtime_error(
"CUDA error transferring primitive indices: " + std::string(cudaGetErrorString(err)));
1851void CollisionDetection::markBVHDirty() {
1853 last_processed_uuids.clear();
1854 last_processed_deleted_uuids.clear();
1855 last_bvh_geometry.clear();
1862#ifdef HELIOS_CUDA_AVAILABLE
1867void CollisionDetection::incrementalUpdateBVH(
const std::set<uint> &added_geometry,
const std::set<uint> &removed_geometry,
const std::set<uint> &final_geometry) {
1870 for (
uint uuid: added_geometry) {
1872 if (printmessages) {
1873 std::cerr <<
"Warning: Added primitive " << uuid <<
" does not exist, falling back to full rebuild" << std::endl;
1875 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
1885 for (
uint uuid: added_geometry) {
1886 updatePrimitiveAABBCache(uuid);
1890 for (
uint uuid: removed_geometry) {
1891 primitive_aabbs_cache.erase(uuid);
1898 size_t total_changes = added_geometry.size() + removed_geometry.size();
1899 size_t current_size = final_geometry.size();
1902 if (current_size > 0 && !bvh_nodes.empty() && (
float(total_changes) /
float(current_size)) < 0.05f) {
1904 bool insertion_successful =
true;
1907 if (!removed_geometry.empty()) {
1908 primitive_indices.erase(std::remove_if(primitive_indices.begin(), primitive_indices.end(), [&removed_geometry](
uint uuid) { return removed_geometry.find(uuid) != removed_geometry.end(); }), primitive_indices.end());
1912 for (
uint uuid: added_geometry) {
1913 primitive_indices.push_back(uuid);
1918 if (insertion_successful && total_changes < 50) {
1919 if (printmessages) {
1920 std::cout <<
"Using targeted tree update for " << total_changes <<
" changes" << std::endl;
1926 optimizedRebuildBVH(final_geometry);
1932 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
1936 last_bvh_geometry = final_geometry;
1941bool CollisionDetection::validateUUIDs(
const std::vector<uint> &UUIDs)
const {
1945 bool all_valid =
true;
1946 for (
uint UUID: UUIDs) {
1948 warnings.
addWarning(
"primitive_uuid_not_exist",
"Primitive UUID " + std::to_string(UUID) +
" does not exist - skipping");
1953 warnings.
report(std::cerr);
1957bool CollisionDetection::rayPrimitiveIntersection(
const vec3 &origin,
const vec3 &direction,
uint primitive_UUID,
float &distance)
const {
1968 if (vertices.empty()) {
1974 float min_distance = std::numeric_limits<float>::max();
1976 if (type == PRIMITIVE_TYPE_TRIANGLE) {
1978 if (vertices.size() >= 3) {
1979 const vec3 &v0 = vertices[0];
1980 const vec3 &v1 = vertices[1];
1981 const vec3 &v2 = vertices[2];
1984 float a = v0.
x - v1.
x, b = v0.
x - v2.
x, c = direction.
x, d = v0.
x - origin.
x;
1985 float e = v0.
y - v1.
y, f = v0.
y - v2.
y, g = direction.
y, h = v0.
y - origin.
y;
1986 float i = v0.
z - v1.
z, j = v0.
z - v2.
z, k = direction.
z, l = v0.
z - origin.
z;
1988 float m = f * k - g * j, n = h * k - g * l, p = f * l - h * j;
1989 float q = g * i - e * k, s = e * j - f * i;
1991 float denom = a * m + b * q + c * s;
1992 if (std::abs(denom) < 1e-8f) {
1996 float inv_denom = 1.0f / denom;
1998 float e1 = d * m - b * n - c * p;
1999 float beta = e1 * inv_denom;
2002 float r = e * l - h * i;
2003 float e2 = a * n + d * q + c * r;
2004 float gamma = e2 * inv_denom;
2006 if (gamma >= 0.0f && beta + gamma <= 1.0f) {
2007 float e3 = a * p - b * r + d * s;
2008 float t = e3 * inv_denom;
2010 if (t > 1e-8f && t < min_distance) {
2017 }
else if (type == PRIMITIVE_TYPE_PATCH) {
2019 if (vertices.size() >= 4) {
2020 const vec3 &v0 = vertices[0];
2021 const vec3 &v1 = vertices[1];
2022 const vec3 &v2 = vertices[2];
2023 const vec3 &v3 = vertices[3];
2034 float denom = direction * normal;
2035 if (std::abs(denom) > 1e-8f) {
2036 float t = (anchor - origin) * normal / denom;
2038 if (t > 1e-8f && t < 1e8f) {
2040 vec3 p = origin + direction * t;
2041 vec3 d = p - anchor;
2044 float ddota = d * a;
2045 float ddotb = d * b;
2048 if (ddota >= 0.0f && ddota <= (a * a) && ddotb >= 0.0f && ddotb <= (b * b)) {
2050 if (t < min_distance) {
2058 }
else if (type == PRIMITIVE_TYPE_VOXEL) {
2060 if (vertices.size() == 8) {
2062 vec3 aabb_min = vertices[0];
2063 vec3 aabb_max = vertices[0];
2065 for (
int i = 1; i < 8; i++) {
2066 aabb_min.
x = std::min(aabb_min.
x, vertices[i].x);
2067 aabb_min.
y = std::min(aabb_min.
y, vertices[i].y);
2068 aabb_min.
z = std::min(aabb_min.
z, vertices[i].z);
2069 aabb_max.
x = std::max(aabb_max.
x, vertices[i].x);
2070 aabb_max.
y = std::max(aabb_max.
y, vertices[i].y);
2071 aabb_max.
z = std::max(aabb_max.
z, vertices[i].z);
2075 float t_near = -std::numeric_limits<float>::max();
2076 float t_far = std::numeric_limits<float>::max();
2079 for (
int i = 0; i < 3; i++) {
2080 float ray_dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
2081 float ray_orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
2082 float aabb_min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
2083 float aabb_max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
2085 if (std::abs(ray_dir_component) < 1e-8f) {
2087 if (ray_orig_component < aabb_min_component || ray_orig_component > aabb_max_component) {
2092 float t1 = (aabb_min_component - ray_orig_component) / ray_dir_component;
2093 float t2 = (aabb_max_component - ray_orig_component) / ray_dir_component;
2101 t_near = std::max(t_near, t1);
2102 t_far = std::min(t_far, t2);
2105 if (t_near > t_far) {
2112 if (t_far >= 0.0f && t_near < min_distance) {
2114 float intersection_distance = (t_near >= 1e-8f) ? t_near : t_far;
2115 if (intersection_distance >= 1e-8f) {
2116 min_distance = intersection_distance;
2124 distance = min_distance;
2129 }
catch (
const std::exception &e) {
2137 std::vector<uint> uuids_to_process = UUIDs.empty() ? context->
getAllUUIDs() : UUIDs;
2140 std::vector<uint> planar_primitives;
2141 for (
uint uuid: uuids_to_process) {
2143 planar_primitives.push_back(uuid);
2156 if (i < 0 || i >=
static_cast<int>(grid_cells.size()) || j < 0 || j >=
static_cast<int>(grid_cells[i].size()) || k < 0 || k >=
static_cast<int>(grid_cells[i][j].size())) {
2157 helios_runtime_error(
"ERROR (CollisionDetection::getGridIntersections): Grid indices out of bounds");
2159 return grid_cells[i][j][k];
2163 if (printmessages) {
2164 std::cerr <<
"WARNING: optimizeLayout not yet implemented" << std::endl;
2169int CollisionDetection::countRayIntersections(
const vec3 &origin,
const vec3 &direction,
float max_distance) {
2171 int intersection_count = 0;
2173 if (bvh_nodes.empty()) {
2174 return intersection_count;
2179 float min_distance = 0.05f;
2185 std::vector<uint> node_stack;
2186 node_stack.push_back(0);
2188 while (!node_stack.empty()) {
2189 uint node_idx = node_stack.back();
2190 node_stack.pop_back();
2192 if (node_idx >= bvh_nodes.size())
2195 const BVHNode &node = bvh_nodes[node_idx];
2199 if (!rayAABBIntersect(origin, direction, node.aabb_min, node.aabb_max, t_min, t_max)) {
2204 if (t_max < min_distance) {
2207 if (max_distance > 0.0f && t_min > max_distance) {
2213 for (
uint i = 0; i < node.primitive_count; i++) {
2214 uint primitive_id = primitive_indices[node.primitive_start + i];
2220 vec3 prim_min, prim_max;
2224 float prim_t_min, prim_t_max;
2225 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2227 bool within_min_distance = prim_t_min >= min_distance;
2228 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2230 if (within_min_distance && within_max_distance) {
2231 intersection_count++;
2237 if (node.left_child != 0xFFFFFFFF) {
2238 node_stack.push_back(node.left_child);
2240 if (node.right_child != 0xFFFFFFFF) {
2241 node_stack.push_back(node.right_child);
2246 return intersection_count;
2249bool CollisionDetection::findNearestRayIntersection(
const vec3 &origin,
const vec3 &direction,
const std::set<uint> &candidate_UUIDs,
float &nearest_distance,
float max_distance) {
2251 nearest_distance = std::numeric_limits<float>::max();
2252 bool found_intersection =
false;
2255 bool check_static_bvh = hierarchical_bvh_enabled && static_bvh_valid && !static_bvh_nodes.empty();
2256 bool check_dynamic_bvh = !bvh_nodes.empty();
2258 if (!check_static_bvh && !check_dynamic_bvh) {
2267 auto traverseBVH = [&](
const std::vector<BVHNode> &nodes,
const std::vector<uint> &primitives,
const char *bvh_name) {
2272 std::vector<uint> node_stack;
2273 node_stack.push_back(0);
2275 while (!node_stack.empty()) {
2276 uint node_idx = node_stack.back();
2277 node_stack.pop_back();
2279 if (node_idx >= nodes.size()) {
2283 const BVHNode &node = nodes[node_idx];
2287 if (!rayAABBIntersect(origin, direction, node.aabb_min, node.aabb_max, t_min, t_max)) {
2292 if (max_distance > 0.0f && t_min > max_distance) {
2297 if (t_min > nearest_distance) {
2303 for (
uint i = 0; i < node.primitive_count; i++) {
2304 uint primitive_id = primitives[node.primitive_start + i];
2308 if (!candidate_UUIDs.empty() && candidate_UUIDs.find(primitive_id) == candidate_UUIDs.end()) {
2318 vec3 prim_min, prim_max;
2322 float prim_t_min, prim_t_max;
2323 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2325 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2327 if (within_max_distance && prim_t_min > 0.0f && prim_t_min < nearest_distance) {
2330 nearest_distance = prim_t_min;
2331 found_intersection =
true;
2337 if (node.left_child != 0xFFFFFFFF) {
2338 node_stack.push_back(node.left_child);
2340 if (node.right_child != 0xFFFFFFFF) {
2341 node_stack.push_back(node.right_child);
2348 if (check_static_bvh) {
2349 traverseBVH(static_bvh_nodes, static_bvh_primitives,
"static");
2353 if (check_dynamic_bvh) {
2354 traverseBVH(bvh_nodes, primitive_indices,
"dynamic");
2357 return found_intersection;
2365 if (candidate_UUIDs.empty()) {
2366 warnings.
addWarning(
"no_candidate_uuids",
"No candidate UUIDs provided");
2367 warnings.
report(std::cerr);
2372 float dir_magnitude = direction.
magnitude();
2373 if (std::abs(dir_magnitude - 1.0f) > 1e-6f) {
2374 warnings.
addWarning(
"direction_not_normalized",
"Direction vector is not normalized (magnitude = " + std::to_string(dir_magnitude) +
")");
2375 warnings.
report(std::cerr);
2381 std::vector<uint> valid_candidates;
2382 for (
uint uuid: candidate_UUIDs) {
2384 valid_candidates.push_back(uuid);
2386 warnings.
addWarning(
"invalid_candidate_uuid",
"Skipping invalid UUID " + std::to_string(uuid));
2391 if (valid_candidates.empty()) {
2392 warnings.
addWarning(
"no_valid_candidates",
"No valid candidate UUIDs after filtering");
2393 warnings.
report(std::cerr);
2397 float nearest_distance_found = std::numeric_limits<float>::max();
2398 vec3 nearest_obstacle_direction;
2399 bool found_forward_surface =
false;
2402 for (
uint primitive_id: valid_candidates) {
2407 if (vertices.empty()) {
2412 vec3 point_on_plane = vertices[0];
2415 vec3 to_origin = origin - point_on_plane;
2416 float distance_to_plane = to_origin * surface_normal;
2419 float surface_distance = std::abs(distance_to_plane);
2422 vec3 surface_direction;
2423 if (distance_to_plane > 0) {
2425 surface_direction = -surface_normal;
2428 surface_direction = surface_normal;
2432 float dot_product = surface_direction * direction;
2434 if (dot_product > 0.0f) {
2435 if (surface_distance < nearest_distance_found) {
2436 nearest_distance_found = surface_distance;
2437 nearest_obstacle_direction = surface_direction;
2438 found_forward_surface =
true;
2443 if (found_forward_surface) {
2444 distance = nearest_distance_found;
2445 obstacle_direction = nearest_obstacle_direction;
2446 warnings.
report(std::cerr);
2450 warnings.
report(std::cerr);
2460 std::vector<uint> effective_candidates;
2461 if (tree_based_bvh_enabled) {
2463 float spatial_filter_distance = height * 1.25f;
2467 effective_candidates = candidate_UUIDs;
2470 if (effective_candidates.empty()) {
2475 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
2476 warnings.
addWarning(
"invalid_half_angle",
"Invalid half_angle " + std::to_string(half_angle));
2477 warnings.
report(std::cerr);
2481 if (height <= 0.0f) {
2482 warnings.
addWarning(
"invalid_height",
"Invalid height " + std::to_string(height));
2483 warnings.
report(std::cerr);
2491 if (bvh_nodes.empty()) {
2496 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
2499 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
2501 float nearest_distance = std::numeric_limits<float>::max();
2502 vec3 nearest_direction;
2503 bool found_obstacle =
false;
2506 std::vector<RayQuery> ray_queries;
2507 ray_queries.reserve(ray_directions.size());
2509 for (
const vec3 &ray_dir: ray_directions) {
2510 ray_queries.emplace_back(apex, ray_dir, height, candidate_UUIDs);
2515 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2518 for (
size_t i = 0; i < hit_results.size(); ++i) {
2519 const HitResult &result = hit_results[i];
2521 if (result.
hit && result.
distance < nearest_distance) {
2522 nearest_distance = result.
distance;
2523 nearest_direction = ray_directions[i];
2524 found_obstacle =
true;
2528 if (found_obstacle) {
2529 distance = nearest_distance;
2530 obstacle_direction = nearest_direction;
2531 warnings.
report(std::cerr);
2535 warnings.
report(std::cerr);
2540 vec3 &obstacle_direction,
int num_rays) {
2546 std::vector<uint> effective_candidates;
2547 if (tree_based_bvh_enabled) {
2551 if (printmessages && !effective_candidates.empty()) {
2552 std::cout <<
"Per-tree findNearestSolidObstacleInCone: Using " << effective_candidates.size() <<
" relevant targets instead of " << candidate_UUIDs.size() <<
" total targets" << std::endl;
2555 effective_candidates = candidate_UUIDs;
2558 if (effective_candidates.empty()) {
2563 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
2564 warnings.
addWarning(
"invalid_half_angle",
"Invalid half_angle " + std::to_string(half_angle));
2565 warnings.
report(std::cerr);
2569 if (height <= 0.0f) {
2570 warnings.
addWarning(
"invalid_height",
"Invalid height " + std::to_string(height));
2571 warnings.
report(std::cerr);
2579 if (bvh_nodes.empty()) {
2584 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
2587 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
2589 float nearest_distance = std::numeric_limits<float>::max();
2590 vec3 nearest_direction;
2591 bool found_obstacle =
false;
2594 std::vector<RayQuery> ray_queries;
2595 ray_queries.reserve(ray_directions.size());
2597 for (
const vec3 &ray_dir: ray_directions) {
2598 ray_queries.emplace_back(apex, ray_dir, height, effective_candidates);
2603 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2606 for (
size_t i = 0; i < hit_results.size(); ++i) {
2607 const HitResult &result = hit_results[i];
2609 if (result.
hit && result.
distance < nearest_distance) {
2610 nearest_distance = result.
distance;
2611 nearest_direction = ray_directions[i];
2612 found_obstacle =
true;
2616 if (found_obstacle) {
2617 distance = nearest_distance;
2618 obstacle_direction = nearest_direction;
2619 warnings.
report(std::cerr);
2623 warnings.
report(std::cerr);
2628std::vector<helios::vec3> CollisionDetection::sampleDirectionsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
int num_samples) {
2630 std::vector<vec3> directions;
2631 directions.reserve(num_samples);
2633 if (num_samples <= 0 || half_angle <= 0.0f) {
2638 vec3 axis = central_axis;
2643 if (std::abs(axis.
z) < 0.9f) {
2653 std::random_device rd;
2654 std::mt19937 gen(rd());
2655 std::uniform_real_distribution<float> uniform_dist(0.0f, 1.0f);
2657 int samples_generated = 0;
2658 int max_attempts = num_samples * 10;
2661 while (samples_generated < num_samples && attempts < max_attempts) {
2665 float u1 = uniform_dist(gen);
2666 float u2 = uniform_dist(gen);
2669 if (samples_generated > 0) {
2670 float stratum_u1 = (float) samples_generated / (
float) num_samples;
2671 float stratum_u2 = uniform_dist(gen);
2672 u1 = (stratum_u1 + u1 / (float) num_samples);
2680 float cos_half_angle = cosf(half_angle);
2681 float cos_theta = cos_half_angle + u1 * (1.0f - cos_half_angle);
2682 float sin_theta = sqrtf(1.0f - cos_theta * cos_theta);
2683 float phi = 2.0f *
M_PI * u2;
2686 float x = sin_theta * cosf(phi);
2687 float y = sin_theta * sinf(phi);
2688 float z = cos_theta;
2692 vec3 world_direction = u * local_direction.
x + v * local_direction.
y + axis * local_direction.
z;
2696 float dot_product = world_direction * axis;
2697 if (dot_product >= cos_half_angle - 1e-6f) {
2698 directions.push_back(world_direction);
2699 samples_generated++;
2704 while (directions.size() < (
size_t) num_samples) {
2705 directions.push_back(axis);
2714std::vector<uint> CollisionDetection::getCandidatesUsingSpatialGrid(
const Cone &cone,
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
2716 vec3 cone_base = apex + central_axis * height;
2717 float cone_base_radius = height * tan(half_angle);
2719 vec3 cone_aabb_min =
vec3(std::min(apex.
x, cone_base.
x - cone_base_radius), std::min(apex.
y, cone_base.
y - cone_base_radius), std::min(apex.
z, cone_base.
z - cone_base_radius));
2720 vec3 cone_aabb_max =
vec3(std::max(apex.
x, cone_base.
x + cone_base_radius), std::max(apex.
y, cone_base.
y + cone_base_radius), std::max(apex.
z, cone_base.
z + cone_base_radius));
2723 std::vector<uint> all_primitives;
2724 if (!primitive_indices.empty()) {
2725 all_primitives = primitive_indices;
2732 return filterPrimitivesParallel(cone, all_primitives);
2737std::vector<uint> CollisionDetection::getCandidatePrimitivesInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
2738 std::vector<uint> candidates;
2741 Cone cone{apex, central_axis, half_angle, height};
2744 candidates = getCandidatesUsingSpatialGrid(cone, apex, central_axis, half_angle, height);
2752std::vector<CollisionDetection::Gap> CollisionDetection::detectGapsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height,
int num_samples) {
2754 std::vector<Gap> gaps;
2757 std::vector<vec3> sample_directions = sampleDirectionsInCone(apex, central_axis, half_angle, num_samples);
2759 if (sample_directions.empty()) {
2764 std::vector<RaySample> ray_samples;
2765 ray_samples.reserve(sample_directions.size());
2767 float max_distance = (height > 0.0f) ? height : -1.0f;
2770 std::vector<RayQuery> ray_queries;
2771 ray_queries.reserve(sample_directions.size());
2773 for (
const vec3 &direction: sample_directions) {
2774 ray_queries.emplace_back(apex, direction, max_distance);
2778 RayTracingStats ray_stats;
2779 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2782 for (
size_t i = 0; i < hit_results.size(); ++i) {
2784 sample.direction = sample_directions[i];
2786 if (hit_results[i].hit) {
2787 sample.distance = hit_results[i].distance;
2788 sample.is_free =
false;
2790 sample.distance = (max_distance > 0.0f) ? max_distance : 1000.0f;
2791 sample.is_free =
true;
2794 ray_samples.push_back(sample);
2800 std::vector<std::pair<float, size_t>> angular_positions;
2801 for (
size_t i = 0; i < ray_samples.size(); ++i) {
2802 if (ray_samples[i].is_free) {
2804 float dot_product = ray_samples[i].direction * central_axis;
2805 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2806 float angular_from_center = acosf(dot_product);
2807 angular_positions.push_back({angular_from_center, i});
2811 if (angular_positions.empty()) {
2816 std::sort(angular_positions.begin(), angular_positions.end());
2819 std::vector<bool> processed(ray_samples.size(),
false);
2820 float min_gap_angular_size = half_angle * 0.05f;
2822 for (
size_t start = 0; start < angular_positions.size(); ++start) {
2823 size_t start_idx = angular_positions[start].second;
2824 if (processed[start_idx])
2828 new_gap.sample_indices.push_back(start_idx);
2829 processed[start_idx] =
true;
2832 std::vector<float> distances_to_start;
2833 for (
size_t j = 0; j < ray_samples.size(); ++j) {
2834 if (j != start_idx && ray_samples[j].is_free && !processed[j]) {
2835 float dot_product = ray_samples[start_idx].direction * ray_samples[j].direction;
2836 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2837 float angular_distance = acosf(dot_product);
2838 distances_to_start.push_back(angular_distance);
2840 distances_to_start.push_back(999.0f);
2845 float sample_density = 2.0f * half_angle / sqrtf((
float) num_samples);
2846 float adaptive_threshold = sample_density * 3.0f;
2848 for (
size_t j = 0; j < ray_samples.size(); ++j) {
2849 if (j != start_idx && ray_samples[j].is_free && !processed[j] && distances_to_start[j] < adaptive_threshold) {
2850 new_gap.sample_indices.push_back(j);
2851 processed[j] =
true;
2856 if (new_gap.sample_indices.size() >= 5) {
2857 gaps.push_back(new_gap);
2862 for (Gap &gap: gaps) {
2864 vec3 center(0, 0, 0);
2865 for (
int idx: gap.sample_indices) {
2866 center = center + ray_samples[idx].direction;
2868 center = center / (float) gap.sample_indices.size();
2870 gap.center_direction = center;
2873 std::vector<RaySample> gap_samples;
2874 for (
int idx: gap.sample_indices) {
2875 gap_samples.push_back(ray_samples[idx]);
2877 gap.angular_size = calculateGapAngularSize(gap_samples, central_axis);
2880 float dot_product = gap.center_direction * central_axis;
2881 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2882 gap.angular_distance = acosf(dot_product);
2887 if (gaps.size() > 10) {
2888 float max_angular_distance = half_angle * 0.8f;
2890 auto it = std::remove_if(gaps.begin(), gaps.end(), [max_angular_distance](
const Gap &gap) { return gap.angular_distance > max_angular_distance; });
2892 gaps.erase(it, gaps.end());
2895 if (gaps.size() < 3 && gaps.size() > 0) {
2897 std::partial_sort(gaps.begin(), gaps.begin() + std::min(
size_t(3), gaps.size()), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.angular_distance < b.angular_distance; });
2905float CollisionDetection::calculateGapAngularSize(
const std::vector<RaySample> &gap_samples,
const vec3 ¢ral_axis) {
2907 if (gap_samples.empty()) {
2912 float min_angle =
M_PI;
2913 float max_angle = 0.0f;
2915 for (
const RaySample &sample: gap_samples) {
2916 float dot_product = sample.direction * central_axis;
2917 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2918 float angle = acosf(dot_product);
2920 min_angle = std::min(min_angle, angle);
2921 max_angle = std::max(max_angle, angle);
2925 float angular_width = max_angle - min_angle;
2929 float solid_angle =
M_PI * angular_width * angular_width;
2934void CollisionDetection::scoreGapsByFishEyeMetric(std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
2937 if (gaps.size() <= 10) {
2938 for (Gap &gap: gaps) {
2941 float size_score = log(1.0f + gap.angular_size * 100.0f);
2943 float distance_penalty = exp(gap.angular_distance * 2.0f);
2945 gap.score = size_score / distance_penalty;
2948 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
2954 const size_t max_gaps_needed = std::min(
size_t(5), gaps.size());
2957 for (Gap &gap: gaps) {
2961 float size_score = log(1.0f + gap.angular_size * 100.0f);
2964 float distance_penalty = exp(gap.angular_distance * 2.0f);
2967 gap.score = size_score / distance_penalty;
2971 std::partial_sort(gaps.begin(), gaps.begin() + max_gaps_needed, gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
2974 gaps.resize(max_gaps_needed);
2977helios::vec3 CollisionDetection::findOptimalGapDirection(
const std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
2981 vec3 result = central_axis;
2987 const Gap &best_gap = gaps[0];
2988 return best_gap.center_direction;
2995 std::vector<std::pair<uint, uint>> collision_pairs;
2999 for (
uint target_id: target_UUIDs) {
3000 if (primitive_centroids_cache.find(target_id) == primitive_centroids_cache.end()) {
3003 if (!vertices.empty()) {
3005 for (
const vec3 &vertex: vertices) {
3006 centroid = centroid + vertex;
3008 centroid = centroid / float(vertices.size());
3009 primitive_centroids_cache[target_id] = centroid;
3015 for (
uint query_id: query_UUIDs) {
3018 if (query_vertices.empty())
3022 for (
const vec3 &vertex: query_vertices) {
3023 query_centroid = query_centroid + vertex;
3025 query_centroid = query_centroid / float(query_vertices.size());
3028 for (
uint target_id: target_UUIDs) {
3029 if (query_id == target_id)
3032 auto target_centroid_it = primitive_centroids_cache.find(target_id);
3033 if (target_centroid_it != primitive_centroids_cache.end()) {
3034 vec3 target_centroid = target_centroid_it->second;
3035 float distance = (query_centroid - target_centroid).magnitude();
3037 if (distance <= max_distance) {
3039 std::vector<uint> single_query = {query_id};
3040 std::vector<uint> single_target = {target_id};
3041 std::vector<uint> empty_objects;
3043 std::vector<uint> collisions =
findCollisions(single_query, empty_objects, single_target, empty_objects);
3044 if (!collisions.empty()) {
3045 collision_pairs.push_back(std::make_pair(query_id, target_id));
3052 return collision_pairs;
3056 if (distance <= 0.0f) {
3057 helios_runtime_error(
"ERROR (CollisionDetection::setMaxCollisionDistance): Distance must be positive");
3060 max_collision_distance = distance;
3064 return max_collision_distance;
3069 std::vector<uint> filtered_UUIDs;
3072 std::vector<uint> candidates;
3073 if (candidate_UUIDs.empty()) {
3076 candidates = candidate_UUIDs;
3080 for (
uint candidate_id: candidates) {
3088 auto cache_it = primitive_centroids_cache.find(candidate_id);
3089 if (cache_it != primitive_centroids_cache.end()) {
3090 centroid = cache_it->second;
3094 if (vertices.empty())
3098 for (
const vec3 &vertex: vertices) {
3099 centroid = centroid + vertex;
3101 centroid = centroid / float(vertices.size());
3102 primitive_centroids_cache[candidate_id] = centroid;
3106 float distance = (query_center - centroid).magnitude();
3107 if (distance <= max_radius) {
3108 filtered_UUIDs.push_back(candidate_id);
3112 return filtered_UUIDs;
3122 if (ray_origins.size() != ray_directions.size()) {
3123 helios_runtime_error(
"ERROR (CollisionDetection::calculateVoxelRayPathLengths): ray_origins and ray_directions vectors must have same size");
3126 if (ray_origins.empty()) {
3127 warnings.
addWarning(
"no_rays_provided",
"No rays provided");
3128 warnings.
report(std::cerr);
3133 initializeVoxelData(grid_center, grid_size, grid_divisions);
3138 if (primitive_cache.empty()) {
3139 buildPrimitiveCache();
3143#ifdef HELIOS_CUDA_AVAILABLE
3146 bool gpu_success = calculateVoxelRayPathLengths_GPU(ray_origins, ray_directions);
3149 if (printmessages) {
3150 warnings.
addWarning(
"gpu_voxel_fallback",
"GPU voxel calculation failed, falling back to CPU");
3152 gpu_acceleration_enabled =
false;
3153 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3156 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3159 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3162 warnings.
report(std::cerr);
3166 if (!validateVoxelIndices(ijk)) {
3167 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Invalid voxel indices");
3170 if (!voxel_data_initialized) {
3171 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3174 if (use_flat_arrays) {
3175 size_t flat_idx = flatIndex(ijk);
3176 voxel_ray_counts_flat[flat_idx] = P_denom;
3177 voxel_transmitted_flat[flat_idx] = P_trans;
3179 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = P_denom;
3180 voxel_transmitted[ijk.
x][ijk.
y][ijk.
z] = P_trans;
3185 if (!validateVoxelIndices(ijk)) {
3186 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelTransmissionProbability): Invalid voxel indices");
3189 if (!voxel_data_initialized) {
3195 if (use_flat_arrays) {
3196 size_t flat_idx = flatIndex(ijk);
3197 P_denom = voxel_ray_counts_flat[flat_idx];
3198 P_trans = voxel_transmitted_flat[flat_idx];
3200 P_denom = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3201 P_trans = voxel_transmitted[ijk.
x][ijk.
y][ijk.
z];
3206 if (!validateVoxelIndices(ijk)) {
3210 if (!voxel_data_initialized) {
3211 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelRbar): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3214 if (use_flat_arrays) {
3215 size_t flat_idx = flatIndex(ijk);
3217 int ray_count = voxel_ray_counts_flat[flat_idx];
3218 if (ray_count == 0) {
3220 voxel_ray_counts_flat[flat_idx] = 1;
3222 voxel_path_lengths_flat[flat_idx] = r_bar *
static_cast<float>(ray_count);
3225 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3226 if (ray_count == 0) {
3228 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = 1;
3230 voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] = r_bar *
static_cast<float>(ray_count);
3235 if (!validateVoxelIndices(ijk)) {
3239 if (!voxel_data_initialized) {
3243 if (use_flat_arrays) {
3244 size_t flat_idx = flatIndex(ijk);
3245 int ray_count = voxel_ray_counts_flat[flat_idx];
3246 if (ray_count == 0) {
3251 return voxel_path_lengths_flat[flat_idx] /
static_cast<float>(ray_count);
3253 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3254 if (ray_count == 0) {
3259 return voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] /
static_cast<float>(ray_count);
3264 if (!validateVoxelIndices(ijk)) {
3265 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayHitCounts): Invalid voxel indices");
3268 if (!voxel_data_initialized) {
3275 if (use_flat_arrays) {
3276 size_t flat_idx = flatIndex(ijk);
3277 hit_before = voxel_hit_before_flat[flat_idx];
3278 hit_after = voxel_hit_after_flat[flat_idx];
3279 hit_inside = voxel_hit_inside_flat[flat_idx];
3281 hit_before = voxel_hit_before[ijk.
x][ijk.
y][ijk.
z];
3282 hit_after = voxel_hit_after[ijk.
x][ijk.
y][ijk.
z];
3283 hit_inside = voxel_hit_inside[ijk.
x][ijk.
y][ijk.
z];
3288 if (!validateVoxelIndices(ijk)) {
3289 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayPathLengths): Invalid voxel indices");
3292 if (!voxel_data_initialized) {
3293 return std::vector<float>();
3296 if (use_flat_arrays) {
3298 size_t flat_idx = flatIndex(ijk);
3301 if (flat_idx >= voxel_individual_path_offsets.size() || flat_idx >= voxel_individual_path_counts.size()) {
3302 return std::vector<float>();
3305 size_t offset = voxel_individual_path_offsets[flat_idx];
3306 size_t count = voxel_individual_path_counts[flat_idx];
3309 if (count == 0 || offset + count > voxel_individual_path_lengths_flat.size()) {
3310 return std::vector<float>();
3313 std::vector<float> result;
3314 result.reserve(count);
3316 for (
size_t i = 0; i < count; ++i) {
3317 result.push_back(voxel_individual_path_lengths_flat[offset + i]);
3322 return voxel_individual_path_lengths[ijk.
x][ijk.
y][ijk.
z];
3327 voxel_ray_counts.clear();
3328 voxel_transmitted.clear();
3329 voxel_path_lengths.clear();
3330 voxel_hit_before.clear();
3331 voxel_hit_after.clear();
3332 voxel_hit_inside.clear();
3333 voxel_individual_path_lengths.clear();
3334 voxel_data_initialized =
false;
3336 if (printmessages) {
3337 std::cout <<
"Voxel data cleared." << std::endl;
3343void CollisionDetection::initializeVoxelData(
const vec3 &grid_center,
const vec3 &grid_size,
const helios::int3 &grid_divisions) {
3346 bool need_reinit = !voxel_data_initialized || (grid_center - voxel_grid_center).magnitude() > 1e-6 || (grid_size - voxel_grid_size).magnitude() > 1e-6 || grid_divisions.
x != voxel_grid_divisions.
x || grid_divisions.
y != voxel_grid_divisions.
y ||
3347 grid_divisions.
z != voxel_grid_divisions.
z;
3351 if (use_flat_arrays) {
3353 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3354 std::fill(voxel_ray_counts_flat.begin(), voxel_ray_counts_flat.end(), 0);
3355 std::fill(voxel_transmitted_flat.begin(), voxel_transmitted_flat.end(), 0);
3356 std::fill(voxel_path_lengths_flat.begin(), voxel_path_lengths_flat.end(), 0.0f);
3357 std::fill(voxel_hit_before_flat.begin(), voxel_hit_before_flat.end(), 0);
3358 std::fill(voxel_hit_after_flat.begin(), voxel_hit_after_flat.end(), 0);
3359 std::fill(voxel_hit_inside_flat.begin(), voxel_hit_inside_flat.end(), 0);
3362 voxel_individual_path_lengths_flat.clear();
3363 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
3364 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
3367 for (
int i = 0; i < grid_divisions.
x; i++) {
3368 for (
int j = 0; j < grid_divisions.
y; j++) {
3369 for (
int k = 0; k < grid_divisions.
z; k++) {
3370 voxel_ray_counts[i][j][k] = 0;
3371 voxel_transmitted[i][j][k] = 0;
3372 voxel_path_lengths[i][j][k] = 0.0f;
3373 voxel_hit_before[i][j][k] = 0;
3374 voxel_hit_after[i][j][k] = 0;
3375 voxel_hit_inside[i][j][k] = 0;
3376 voxel_individual_path_lengths[i][j][k].clear();
3385 voxel_grid_center = grid_center;
3386 voxel_grid_size = grid_size;
3387 voxel_grid_divisions = grid_divisions;
3390 use_flat_arrays =
true;
3392 if (use_flat_arrays) {
3394 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3396 voxel_ray_counts_flat.assign(total_voxels, 0);
3397 voxel_transmitted_flat.assign(total_voxels, 0);
3398 voxel_path_lengths_flat.assign(total_voxels, 0.0f);
3399 voxel_hit_before_flat.assign(total_voxels, 0);
3400 voxel_hit_after_flat.assign(total_voxels, 0);
3401 voxel_hit_inside_flat.assign(total_voxels, 0);
3404 voxel_individual_path_lengths_flat.clear();
3405 voxel_individual_path_offsets.assign(total_voxels, 0);
3406 voxel_individual_path_counts.assign(total_voxels, 0);
3409 voxel_individual_path_lengths_flat.reserve(total_voxels * 10);
3413 voxel_ray_counts.resize(grid_divisions.
x);
3414 voxel_transmitted.resize(grid_divisions.
x);
3415 voxel_path_lengths.resize(grid_divisions.
x);
3416 voxel_hit_before.resize(grid_divisions.
x);
3417 voxel_hit_after.resize(grid_divisions.
x);
3418 voxel_hit_inside.resize(grid_divisions.
x);
3419 voxel_individual_path_lengths.resize(grid_divisions.
x);
3421 for (
int i = 0; i < grid_divisions.
x; i++) {
3422 voxel_ray_counts[i].resize(grid_divisions.
y);
3423 voxel_transmitted[i].resize(grid_divisions.
y);
3424 voxel_path_lengths[i].resize(grid_divisions.
y);
3425 voxel_hit_before[i].resize(grid_divisions.
y);
3426 voxel_hit_after[i].resize(grid_divisions.
y);
3427 voxel_hit_inside[i].resize(grid_divisions.
y);
3428 voxel_individual_path_lengths[i].resize(grid_divisions.
y);
3430 for (
int j = 0; j < grid_divisions.
y; j++) {
3431 voxel_ray_counts[i][j].resize(grid_divisions.
z, 0);
3432 voxel_transmitted[i][j].resize(grid_divisions.
z, 0);
3433 voxel_path_lengths[i][j].resize(grid_divisions.
z, 0.0f);
3434 voxel_hit_before[i][j].resize(grid_divisions.
z, 0);
3435 voxel_hit_after[i][j].resize(grid_divisions.
z, 0);
3436 voxel_hit_inside[i][j].resize(grid_divisions.
z, 0);
3437 voxel_individual_path_lengths[i][j].resize(grid_divisions.
z);
3442 voxel_data_initialized =
true;
3445bool CollisionDetection::validateVoxelIndices(
const helios::int3 &ijk)
const {
3446 return (ijk.
x >= 0 && ijk.
x < voxel_grid_divisions.
x && ijk.
y >= 0 && ijk.
y < voxel_grid_divisions.
y && ijk.
z >= 0 && ijk.
z < voxel_grid_divisions.
z);
3449void CollisionDetection::calculateVoxelAABB(
const helios::int3 &ijk,
vec3 &voxel_min,
vec3 &voxel_max)
const {
3450 vec3 voxel_size =
make_vec3(voxel_grid_size.
x /
static_cast<float>(voxel_grid_divisions.
x), voxel_grid_size.
y /
static_cast<float>(voxel_grid_divisions.
y), voxel_grid_size.
z /
static_cast<float>(voxel_grid_divisions.
z));
3452 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3454 voxel_min = grid_min +
make_vec3(
static_cast<float>(ijk.
x) * voxel_size.
x,
static_cast<float>(ijk.
y) * voxel_size.
y,
static_cast<float>(ijk.
z) * voxel_size.
z);
3456 voxel_max = voxel_min + voxel_size;
3459std::vector<std::pair<helios::int3, float>> CollisionDetection::traverseVoxelGrid(
const vec3 &ray_origin,
const vec3 &ray_direction)
const {
3460 std::vector<std::pair<helios::int3, float>> traversed_voxels;
3463 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3464 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
3465 vec3 voxel_size =
make_vec3(voxel_grid_size.x /
static_cast<float>(voxel_grid_divisions.
x), voxel_grid_size.y /
static_cast<float>(voxel_grid_divisions.
y), voxel_grid_size.z /
static_cast<float>(voxel_grid_divisions.
z));
3468 float t_grid_min, t_grid_max;
3469 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max)) {
3470 return traversed_voxels;
3474 if (t_grid_max <= 1e-6) {
3475 return traversed_voxels;
3479 t_grid_min = std::max(0.0f, t_grid_min);
3482 if (voxel_grid_divisions.
x == 1 && voxel_grid_divisions.
y == 1 && voxel_grid_divisions.
z == 1) {
3483 float path_length = t_grid_max - t_grid_min;
3484 if (path_length > 1e-6f) {
3485 traversed_voxels.emplace_back(helios::make_int3(0, 0, 0), path_length);
3487 return traversed_voxels;
3491 vec3 start_pos = ray_origin + t_grid_min * ray_direction;
3495 current_voxel.
x =
static_cast<int>(std::floor((start_pos.
x - grid_min.
x) / voxel_size.
x));
3496 current_voxel.
y =
static_cast<int>(std::floor((start_pos.
y - grid_min.
y) / voxel_size.
y));
3497 current_voxel.
z =
static_cast<int>(std::floor((start_pos.
z - grid_min.
z) / voxel_size.
z));
3500 current_voxel.
x = std::max(0, std::min(current_voxel.
x, voxel_grid_divisions.
x - 1));
3501 current_voxel.
y = std::max(0, std::min(current_voxel.
y, voxel_grid_divisions.
y - 1));
3502 current_voxel.
z = std::max(0, std::min(current_voxel.
z, voxel_grid_divisions.
z - 1));
3506 vec3 t_delta, t_max;
3509 for (
int i = 0; i < 3; i++) {
3510 float dir_comp = (i == 0) ? ray_direction.
x : (i == 1) ? ray_direction.y : ray_direction.z;
3511 float size_comp = (i == 0) ? voxel_size.
x : (i == 1) ? voxel_size.y : voxel_size.z;
3512 float grid_min_comp = (i == 0) ? grid_min.
x : (i == 1) ? grid_min.y : grid_min.z;
3513 float start_comp = (i == 0) ? start_pos.
x : (i == 1) ? start_pos.y : start_pos.z;
3514 int current_comp = (i == 0) ? current_voxel.
x : (i == 1) ? current_voxel.y : current_voxel.z;
3515 int max_comp = (i == 0) ? voxel_grid_divisions.
x : (i == 1) ? voxel_grid_divisions.y : voxel_grid_divisions.z;
3517 if (std::abs(dir_comp) < 1e-8f) {
3523 }
else if (i == 1) {
3535 step.
x = (dir_comp > 0) ? 1 : -1;
3536 t_delta.
x = std::abs(size_comp / dir_comp);
3539 t_max.
x = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3541 t_max.
x = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3543 }
else if (i == 1) {
3544 step.
y = (dir_comp > 0) ? 1 : -1;
3545 t_delta.
y = std::abs(size_comp / dir_comp);
3548 t_max.
y = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3550 t_max.
y = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3553 step.
z = (dir_comp > 0) ? 1 : -1;
3554 t_delta.
z = std::abs(size_comp / dir_comp);
3557 t_max.
z = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3559 t_max.
z = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3566 float current_t = t_grid_min;
3568 while (validateVoxelIndices(current_voxel) && current_t < t_grid_max) {
3570 float next_t = std::min({t_max.
x, t_max.
y, t_max.
z, t_grid_max});
3571 float path_length = next_t - current_t;
3573 if (path_length > 1e-6f) {
3574 traversed_voxels.emplace_back(current_voxel, path_length);
3578 if (next_t >= t_grid_max) {
3583 if (t_max.
x <= t_max.
y && t_max.
x <= t_max.
z) {
3584 current_voxel.
x += step.
x;
3585 t_max.
x += t_delta.
x;
3586 }
else if (t_max.
y <= t_max.
z) {
3587 current_voxel.
y += step.
y;
3588 t_max.
y += t_delta.
y;
3590 current_voxel.
z += step.
z;
3591 t_max.
z += t_delta.
z;
3597 return traversed_voxels;
3600void CollisionDetection::calculateVoxelRayPathLengths_CPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
3603 auto start_time = std::chrono::high_resolution_clock::now();
3606 std::atomic<long long> total_raycast_time(0);
3607 std::atomic<int> raycast_count(0);
3612 const int num_rays =
static_cast<int>(ray_origins.size());
3615 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
3618 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3619 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
3622 const int num_threads = omp_get_max_threads();
3625 std::vector<std::vector<int>> thread_ray_counts(num_threads, std::vector<int>(total_voxels, 0));
3626 std::vector<std::vector<float>> thread_path_lengths(num_threads, std::vector<float>(total_voxels, 0.0f));
3627 std::vector<std::vector<int>> thread_hit_before(num_threads, std::vector<int>(total_voxels, 0));
3628 std::vector<std::vector<int>> thread_hit_after(num_threads, std::vector<int>(total_voxels, 0));
3629 std::vector<std::vector<int>> thread_hit_inside(num_threads, std::vector<int>(total_voxels, 0));
3630 std::vector<std::vector<int>> thread_transmitted(num_threads, std::vector<int>(total_voxels, 0));
3631 std::vector<std::vector<std::vector<float>>> thread_individual_paths(num_threads, std::vector<std::vector<float>>(total_voxels));
3634#pragma omp parallel for schedule(dynamic)
3635 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
3636 const int thread_id = omp_get_thread_num();
3637 const vec3 &ray_origin = ray_origins[ray_idx];
3638 const vec3 &ray_direction = ray_directions[ray_idx];
3641 float t_grid_min, t_grid_max;
3642 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
3647 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
3650 if (traversed_voxels.empty()) {
3655 auto raycast_start = std::chrono::high_resolution_clock::now();
3656 RayQuery query(ray_origin, ray_direction, -1.0f, {});
3657 HitResult hit =
castRay(query);
3658 auto raycast_end = std::chrono::high_resolution_clock::now();
3661 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
3664 float hit_distance = hit.hit ? hit.distance : 1e30f;
3667 for (
const auto &voxel_data: traversed_voxels) {
3669 float path_length = voxel_data.second;
3672 vec3 voxel_min, voxel_max;
3673 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
3677 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
3684 bool hit_before =
false;
3685 bool hit_after =
false;
3686 bool hit_inside =
false;
3690 if (hit_distance < t_min) {
3693 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
3707 size_t flat_idx = flatIndex(voxel_idx);
3710 thread_ray_counts[thread_id][flat_idx]++;
3711 thread_path_lengths[thread_id][flat_idx] += path_length;
3714 thread_hit_before[thread_id][flat_idx]++;
3717 thread_hit_after[thread_id][flat_idx]++;
3720 thread_hit_inside[thread_id][flat_idx]++;
3722 thread_transmitted[thread_id][flat_idx]++;
3726 thread_individual_paths[thread_id][flat_idx].push_back(path_length);
3732 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3733 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3734 voxel_ray_counts_flat[voxel_idx] += thread_ray_counts[thread_id][voxel_idx];
3735 voxel_path_lengths_flat[voxel_idx] += thread_path_lengths[thread_id][voxel_idx];
3736 voxel_hit_before_flat[voxel_idx] += thread_hit_before[thread_id][voxel_idx];
3737 voxel_hit_after_flat[voxel_idx] += thread_hit_after[thread_id][voxel_idx];
3738 voxel_hit_inside_flat[voxel_idx] += thread_hit_inside[thread_id][voxel_idx];
3739 voxel_transmitted_flat[voxel_idx] += thread_transmitted[thread_id][voxel_idx];
3744 if (use_flat_arrays) {
3746 voxel_individual_path_lengths_flat.clear();
3749 size_t total_paths = 0;
3750 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3751 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3752 total_paths += thread_individual_paths[thread_id][voxel_idx].size();
3755 voxel_individual_path_lengths_flat.reserve(total_paths);
3758 size_t current_offset = 0;
3759 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3760 voxel_individual_path_offsets[voxel_idx] = current_offset;
3761 size_t voxel_path_count = 0;
3764 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3765 for (
float path_length: thread_individual_paths[thread_id][voxel_idx]) {
3766 voxel_individual_path_lengths_flat.push_back(path_length);
3771 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
3772 current_offset += voxel_path_count;
3777 const int num_threads = 1;
3778 const int thread_id = 0;
3781 std::vector<int> serial_ray_counts(total_voxels, 0);
3782 std::vector<float> serial_path_lengths(total_voxels, 0.0f);
3783 std::vector<int> serial_hit_before(total_voxels, 0);
3784 std::vector<int> serial_hit_after(total_voxels, 0);
3785 std::vector<int> serial_hit_inside(total_voxels, 0);
3786 std::vector<int> serial_transmitted(total_voxels, 0);
3787 std::vector<std::vector<float>> serial_individual_paths(total_voxels);
3790 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
3791 const vec3 &ray_origin = ray_origins[ray_idx];
3792 const vec3 &ray_direction = ray_directions[ray_idx];
3795 float t_grid_min, t_grid_max;
3796 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
3801 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
3804 if (traversed_voxels.empty()) {
3809 auto raycast_start = std::chrono::high_resolution_clock::now();
3810 RayQuery query(ray_origin, ray_direction, -1.0f, {});
3811 HitResult hit =
castRay(query);
3812 auto raycast_end = std::chrono::high_resolution_clock::now();
3815 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
3818 float hit_distance = hit.hit ? hit.distance : 1e30f;
3821 for (
const auto &voxel_data: traversed_voxels) {
3823 float path_length = voxel_data.second;
3826 vec3 voxel_min, voxel_max;
3827 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
3831 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
3838 bool hit_before =
false;
3839 bool hit_after =
false;
3840 bool hit_inside =
false;
3844 if (hit_distance < t_min) {
3847 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
3861 size_t flat_idx = flatIndex(voxel_idx);
3863 serial_ray_counts[flat_idx]++;
3864 serial_path_lengths[flat_idx] += path_length;
3867 serial_hit_before[flat_idx]++;
3870 serial_hit_after[flat_idx]++;
3873 serial_hit_inside[flat_idx]++;
3875 serial_transmitted[flat_idx]++;
3879 serial_individual_paths[flat_idx].push_back(path_length);
3884 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3885 voxel_ray_counts_flat[voxel_idx] += serial_ray_counts[voxel_idx];
3886 voxel_path_lengths_flat[voxel_idx] += serial_path_lengths[voxel_idx];
3887 voxel_hit_before_flat[voxel_idx] += serial_hit_before[voxel_idx];
3888 voxel_hit_after_flat[voxel_idx] += serial_hit_after[voxel_idx];
3889 voxel_hit_inside_flat[voxel_idx] += serial_hit_inside[voxel_idx];
3890 voxel_transmitted_flat[voxel_idx] += serial_transmitted[voxel_idx];
3894 if (use_flat_arrays) {
3896 voxel_individual_path_lengths_flat.clear();
3899 size_t total_paths = 0;
3900 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3901 total_paths += serial_individual_paths[voxel_idx].size();
3903 voxel_individual_path_lengths_flat.reserve(total_paths);
3906 size_t current_offset = 0;
3907 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3908 voxel_individual_path_offsets[voxel_idx] = current_offset;
3909 size_t voxel_path_count = serial_individual_paths[voxel_idx].size();
3912 for (
float path_length: serial_individual_paths[voxel_idx]) {
3913 voxel_individual_path_lengths_flat.push_back(path_length);
3916 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
3917 current_offset += voxel_path_count;
3922 auto end_time = std::chrono::high_resolution_clock::now();
3923 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
3926 long long avg_raycast_time = raycast_count > 0 ? total_raycast_time.load() / raycast_count.load() : 0;
3949#ifdef HELIOS_CUDA_AVAILABLE
3950bool CollisionDetection::calculateVoxelRayPathLengths_GPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
3952 int deviceCount = 0;
3953 cudaError_t err = cudaGetDeviceCount(&deviceCount);
3954 if (err != cudaSuccess || deviceCount == 0) {
3959 if (printmessages) {
3962 auto start_time = std::chrono::high_resolution_clock::now();
3964 const int num_rays =
static_cast<int>(ray_origins.size());
3965 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
3968 std::vector<float> h_ray_origins(num_rays * 3);
3969 std::vector<float> h_ray_directions(num_rays * 3);
3970 std::vector<int> h_voxel_ray_counts(total_voxels, 0);
3971 std::vector<float> h_voxel_path_lengths(total_voxels, 0.0f);
3972 std::vector<int> h_voxel_transmitted(total_voxels, 0);
3973 std::vector<int> h_voxel_hit_before(total_voxels, 0);
3974 std::vector<int> h_voxel_hit_after(total_voxels, 0);
3975 std::vector<int> h_voxel_hit_inside(total_voxels, 0);
3978 for (
int i = 0; i < num_rays; i++) {
3979 h_ray_origins[i * 3 + 0] = ray_origins[i].x;
3980 h_ray_origins[i * 3 + 1] = ray_origins[i].y;
3981 h_ray_origins[i * 3 + 2] = ray_origins[i].z;
3983 h_ray_directions[i * 3 + 0] = ray_directions[i].x;
3984 h_ray_directions[i * 3 + 1] = ray_directions[i].y;
3985 h_ray_directions[i * 3 + 2] = ray_directions[i].z;
3989 int primitive_count =
static_cast<int>(primitive_cache.size());
3992 bool gpu_success =
launchVoxelRayPathLengths(num_rays, h_ray_origins.data(), h_ray_directions.data(), voxel_grid_center.
x, voxel_grid_center.
y, voxel_grid_center.
z, voxel_grid_size.x, voxel_grid_size.y, voxel_grid_size.z, voxel_grid_divisions.
x,
3993 voxel_grid_divisions.
y, voxel_grid_divisions.
z, primitive_count, h_voxel_ray_counts.data(), h_voxel_path_lengths.data(), h_voxel_transmitted.data(), h_voxel_hit_before.data(), h_voxel_hit_after.data(),
3994 h_voxel_hit_inside.data());
4002 if (use_flat_arrays) {
4004 voxel_ray_counts_flat = h_voxel_ray_counts;
4005 voxel_path_lengths_flat = h_voxel_path_lengths;
4006 voxel_transmitted_flat = h_voxel_transmitted;
4009 voxel_hit_before_flat = h_voxel_hit_before;
4010 voxel_hit_after_flat = h_voxel_hit_after;
4011 voxel_hit_inside_flat = h_voxel_hit_inside;
4016 voxel_individual_path_lengths_flat.clear();
4017 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
4018 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
4023 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4024 voxel_individual_path_offsets[voxel_idx] = voxel_individual_path_lengths_flat.size();
4026 if (h_voxel_ray_counts[voxel_idx] > 0) {
4029 std::vector<float> estimated_paths;
4032 int voxel_z = voxel_idx % voxel_grid_divisions.
z;
4033 int voxel_y = (voxel_idx / voxel_grid_divisions.
z) % voxel_grid_divisions.
y;
4034 int voxel_x = voxel_idx / (voxel_grid_divisions.
y * voxel_grid_divisions.
z);
4037 vec3 voxel_size = voxel_grid_size;
4038 voxel_size.
x /= voxel_grid_divisions.
x;
4039 voxel_size.
y /= voxel_grid_divisions.
y;
4040 voxel_size.
z /= voxel_grid_divisions.
z;
4042 vec3 voxel_min = voxel_grid_center - voxel_grid_size * 0.5f;
4043 voxel_min.
x += voxel_x * voxel_size.
x;
4044 voxel_min.
y += voxel_y * voxel_size.
y;
4045 voxel_min.
z += voxel_z * voxel_size.
z;
4047 vec3 voxel_max = voxel_min + voxel_size;
4050 for (
int ray_idx = 0; ray_idx < num_rays; ++ray_idx) {
4051 vec3 ray_origin = ray_origins[ray_idx];
4052 vec3 ray_dir = ray_directions[ray_idx];
4056 float t_max = std::numeric_limits<float>::max();
4059 for (
int axis = 0; axis < 3; ++axis) {
4060 float origin_comp = (axis == 0) ? ray_origin.
x : (axis == 1) ? ray_origin.y : ray_origin.z;
4061 float dir_comp = (axis == 0) ? ray_dir.
x : (axis == 1) ? ray_dir.y : ray_dir.z;
4062 float min_comp = (axis == 0) ? voxel_min.
x : (axis == 1) ? voxel_min.y : voxel_min.z;
4063 float max_comp = (axis == 0) ? voxel_max.
x : (axis == 1) ? voxel_max.y : voxel_max.z;
4065 if (std::abs(dir_comp) < 1e-9f) {
4067 if (origin_comp < min_comp || origin_comp > max_comp) {
4072 float t1 = (min_comp - origin_comp) / dir_comp;
4073 float t2 = (max_comp - origin_comp) / dir_comp;
4078 t_min = std::max(t_min, t1);
4079 t_max = std::min(t_max, t2);
4087 if (t_max > t_min && t_max > 0.0f) {
4088 float entry_t = std::max(0.0f, t_min);
4089 float exit_t = t_max;
4090 float path_length = exit_t - entry_t;
4092 if (path_length > 1e-6f) {
4093 estimated_paths.push_back(path_length);
4099 for (
float path_length: estimated_paths) {
4100 voxel_individual_path_lengths_flat.push_back(path_length);
4102 voxel_individual_path_counts[voxel_idx] = estimated_paths.size();
4104 voxel_individual_path_counts[voxel_idx] = 0;
4110 for (
int i = 0; i < voxel_grid_divisions.
x; i++) {
4111 for (
int j = 0; j < voxel_grid_divisions.
y; j++) {
4112 for (
int k = 0; k < voxel_grid_divisions.
z; k++) {
4113 int flat_idx = i * voxel_grid_divisions.
y * voxel_grid_divisions.
z + j * voxel_grid_divisions.
z + k;
4114 voxel_ray_counts[i][j][k] = h_voxel_ray_counts[flat_idx];
4115 voxel_path_lengths[i][j][k] = h_voxel_path_lengths[flat_idx];
4116 voxel_transmitted[i][j][k] = h_voxel_transmitted[flat_idx];
4119 voxel_hit_before[i][j][k] = h_voxel_hit_before[flat_idx];
4120 voxel_hit_after[i][j][k] = h_voxel_hit_after[flat_idx];
4121 voxel_hit_inside[i][j][k] = h_voxel_hit_inside[flat_idx];
4124 voxel_individual_path_lengths[i][j][k].clear();
4125 if (h_voxel_ray_counts[flat_idx] > 0) {
4126 float avg_path_length = h_voxel_path_lengths[flat_idx] / h_voxel_ray_counts[flat_idx];
4127 for (
int ray = 0; ray < h_voxel_ray_counts[flat_idx]; ++ray) {
4128 voxel_individual_path_lengths[i][j][k].push_back(avg_path_length);
4136 auto end_time = std::chrono::high_resolution_clock::now();
4137 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4139 if (printmessages) {
4142 int total_ray_voxel_intersections = 0;
4143 for (
const auto &count: h_voxel_ray_counts) {
4144 total_ray_voxel_intersections += count;
4152void CollisionDetection::ensureOptimizedBVH() {
4155 if (soa_dirty || bvh_nodes_soa.node_count != bvh_nodes.size() || bvh_nodes_soa.aabb_mins.empty()) {
4157 if (bvh_nodes.empty()) {
4158 bvh_nodes_soa.clear();
4159 bvh_nodes_soa.node_count = 0;
4163 size_t node_count = bvh_nodes.size();
4166 bvh_nodes_soa.node_count = node_count;
4167 bvh_nodes_soa.aabb_mins.resize(node_count);
4168 bvh_nodes_soa.aabb_maxs.resize(node_count);
4169 bvh_nodes_soa.left_children.resize(node_count);
4170 bvh_nodes_soa.right_children.resize(node_count);
4171 bvh_nodes_soa.primitive_starts.resize(node_count);
4172 bvh_nodes_soa.primitive_counts.resize(node_count);
4173 bvh_nodes_soa.is_leaf_flags.resize(node_count);
4176 for (
size_t i = 0; i < node_count; ++i) {
4177 const BVHNode &node = bvh_nodes[i];
4180 bvh_nodes_soa.aabb_mins[i] = node.aabb_min;
4181 bvh_nodes_soa.aabb_maxs[i] = node.aabb_max;
4182 bvh_nodes_soa.left_children[i] = node.left_child;
4183 bvh_nodes_soa.right_children[i] = node.right_child;
4186 bvh_nodes_soa.primitive_starts[i] = node.primitive_start;
4187 bvh_nodes_soa.primitive_counts[i] = node.primitive_count;
4188 bvh_nodes_soa.is_leaf_flags[i] = node.is_leaf ? 1 : 0;
4196void CollisionDetection::updatePrimitiveAABBCache(
uint uuid) {
4205 if (vertices.empty()) {
4210 vec3 aabb_min = vertices[0];
4211 vec3 aabb_max = vertices[0];
4213 for (
const vec3 &vertex: vertices) {
4214 aabb_min.
x = std::min(aabb_min.
x, vertex.x);
4215 aabb_min.
y = std::min(aabb_min.
y, vertex.y);
4216 aabb_min.
z = std::min(aabb_min.
z, vertex.z);
4218 aabb_max.
x = std::max(aabb_max.
x, vertex.x);
4219 aabb_max.
y = std::max(aabb_max.
y, vertex.y);
4220 aabb_max.
z = std::max(aabb_max.
z, vertex.z);
4224 primitive_aabbs_cache[uuid] = std::make_pair(aabb_min, aabb_max);
4226 }
catch (
const std::exception &e) {
4227 if (printmessages) {
4228 std::cerr <<
"Warning: Failed to cache AABB for primitive " << uuid <<
": " << e.what() << std::endl;
4233void CollisionDetection::optimizedRebuildBVH(
const std::set<uint> &final_geometry) {
4235 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
4238 for (
uint uuid: final_geometry) {
4239 if (primitive_aabbs_cache.find(uuid) == primitive_aabbs_cache.end()) {
4240 updatePrimitiveAABBCache(uuid);
4244 if (printmessages) {
4245 std::cout <<
"Optimized rebuild with " << final_primitives.size() <<
" primitives (using cached AABBs)" << std::endl;
4252 last_bvh_geometry = final_geometry;
4260 tree_based_bvh_enabled =
true;
4261 tree_isolation_distance = isolation_distance;
4265 tree_based_bvh_enabled =
false;
4266 tree_bvh_map.clear();
4267 object_to_tree_map.clear();
4271 return tree_based_bvh_enabled;
4275 if (static_obstacle_primitives.empty() || obstacle_spatial_grid_initialized) {
4280 obstacle_spatial_grid.cell_size = 20.0f;
4281 obstacle_spatial_grid.grid_cells.clear();
4284 for (
uint obstacle_prim: static_obstacle_primitives) {
4288 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4290 int64_t grid_key = obstacle_spatial_grid.getGridKey(prim_center.
x, prim_center.
y);
4291 obstacle_spatial_grid.grid_cells[grid_key].push_back(obstacle_prim);
4295 obstacle_spatial_grid_initialized =
true;
4298std::vector<uint> CollisionDetection::ObstacleSpatialGrid::getRelevantObstacles(
const helios::vec3 &position,
float radius)
const {
4299 std::vector<uint> relevant_obstacles;
4302 int32_t min_grid_x =
static_cast<int32_t
>(std::floor((position.
x - radius) / cell_size));
4303 int32_t max_grid_x =
static_cast<int32_t
>(std::floor((position.
x + radius) / cell_size));
4304 int32_t min_grid_y =
static_cast<int32_t
>(std::floor((position.
y - radius) / cell_size));
4305 int32_t max_grid_y =
static_cast<int32_t
>(std::floor((position.
y + radius) / cell_size));
4308 for (int32_t grid_x = min_grid_x; grid_x <= max_grid_x; ++grid_x) {
4309 for (int32_t grid_y = min_grid_y; grid_y <= max_grid_y; ++grid_y) {
4310 int64_t grid_key = (
static_cast<int64_t
>(grid_x) << 32) |
static_cast<uint32_t
>(grid_y);
4312 auto cell_it = grid_cells.find(grid_key);
4313 if (cell_it != grid_cells.end()) {
4315 relevant_obstacles.insert(relevant_obstacles.end(), cell_it->second.begin(), cell_it->second.end());
4320 return relevant_obstacles;
4324 if (!tree_based_bvh_enabled) {
4325 if (printmessages) {
4326 std::cout <<
"WARNING: Tree registration ignored - tree-based BVH not enabled" << std::endl;
4332 vec3 tree_center(0, 0, 0);
4333 vec3 aabb_min(1e30f, 1e30f, 1e30f);
4334 vec3 aabb_max(-1e30f, -1e30f, -1e30f);
4336 for (
uint prim_uuid: tree_primitives) {
4338 vec3 prim_min, prim_max;
4341 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
4342 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
4343 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
4345 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
4346 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
4347 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
4351 tree_center = (aabb_min + aabb_max) * 0.5f;
4352 float tree_radius = (aabb_max - aabb_min).magnitude() * 0.5f;
4355 TreeBVH &tree_bvh = tree_bvh_map[tree_object_id];
4356 tree_bvh.tree_object_id = tree_object_id;
4357 tree_bvh.tree_center = tree_center;
4358 tree_bvh.tree_radius = tree_radius;
4361 for (
uint prim_uuid: tree_primitives) {
4362 object_to_tree_map[prim_uuid] = tree_object_id;
4367 static_obstacle_primitives.clear();
4370 for (
uint prim_uuid: obstacle_primitives) {
4372 static_obstacle_primitives.push_back(prim_uuid);
4377 obstacle_spatial_grid_initialized =
false;
4382 std::vector<uint> relevant_geometry;
4384 if (!tree_based_bvh_enabled) {
4386 return relevant_geometry;
4390 const float MAX_STATIC_OBSTACLE_DISTANCE = max_distance;
4392 if (obstacle_spatial_grid_initialized && !static_obstacle_primitives.empty()) {
4394 std::vector<uint> candidate_obstacles = obstacle_spatial_grid.getRelevantObstacles(query_position, MAX_STATIC_OBSTACLE_DISTANCE);
4398 for (
uint static_prim: candidate_obstacles) {
4402 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4403 float distance = (query_position - prim_center).magnitude();
4404 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4405 relevant_geometry.push_back(static_prim);
4412 for (
uint static_prim: static_obstacle_primitives) {
4416 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4417 float distance = (query_position - prim_center).magnitude();
4418 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4419 relevant_geometry.push_back(static_prim);
4426 uint source_tree_id = 0;
4427 if (!query_primitives.empty()) {
4429 auto it = object_to_tree_map.find(query_primitives[0]);
4430 if (it != object_to_tree_map.end()) {
4431 source_tree_id = it->second;
4436 if (source_tree_id == 0) {
4437 float min_distance = 1e30f;
4438 for (
const auto &tree_pair: tree_bvh_map) {
4439 const TreeBVH &tree = tree_pair.second;
4440 float distance = (query_position - tree.tree_center).magnitude();
4441 if (distance < min_distance) {
4442 min_distance = distance;
4443 source_tree_id = tree.tree_object_id;
4449 for (
const auto &tree_pair: tree_bvh_map) {
4450 const TreeBVH &tree = tree_pair.second;
4451 uint tree_id = tree_pair.first;
4453 if (tree_id == source_tree_id) {
4455 for (
uint prim_uuid: tree.primitive_indices) {
4457 relevant_geometry.push_back(prim_uuid);
4462 float distance = (query_position - tree.tree_center).magnitude();
4463 float interaction_threshold = tree_isolation_distance + tree.tree_radius;
4465 if (distance < interaction_threshold) {
4467 for (
uint prim_uuid: tree.primitive_indices) {
4469 relevant_geometry.push_back(prim_uuid);
4476 return relevant_geometry;