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);
54void 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;
107 voxel_data_initialized =
false;
111 use_flat_arrays =
false;
114 max_collision_distance = 10.0f;
118#ifdef HELIOS_CUDA_AVAILABLE
124 return findCollisions(std::vector<uint>{UUID}, allow_spatial_culling);
131 std::cerr <<
"WARNING (CollisionDetection::findCollisions): No UUIDs provided" << std::endl;
137 std::vector<uint> valid_UUIDs;
138 for (
uint uuid: UUIDs) {
140 valid_UUIDs.push_back(uuid);
143 std::cerr <<
"ERROR (CollisionDetection::findCollisions): Invalid UUID " << uuid << std::endl;
145 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Invalid UUID " + std::to_string(uuid) +
" provided");
152 std::vector<uint> all_collisions;
154 for (
uint UUID: valid_UUIDs) {
160 vec3 aabb_min, aabb_max;
163 std::vector<uint> collisions;
165#ifdef HELIOS_CUDA_AVAILABLE
166 if (gpu_acceleration_enabled && gpu_memory_allocated) {
167 collisions = traverseBVH_GPU(aabb_min, aabb_max);
169 collisions = traverseBVH_CPU(aabb_min, aabb_max);
172 collisions = traverseBVH_CPU(aabb_min, aabb_max);
176 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
179 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
183 std::sort(all_collisions.begin(), all_collisions.end());
184 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
186 return all_collisions;
191 if (primitive_UUIDs.empty() && object_IDs.empty()) {
193 std::cerr <<
"WARNING (CollisionDetection::findCollisions): No UUIDs or object IDs provided" << std::endl;
199 std::vector<uint> all_test_UUIDs = primitive_UUIDs;
201 for (
uint ObjID: object_IDs) {
203 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Object ID " + std::to_string(ObjID) +
" does not exist");
207 all_test_UUIDs.insert(all_test_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
213std::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) {
215 if (query_UUIDs.empty() && query_object_IDs.empty()) {
217 std::cerr <<
"WARNING (CollisionDetection::findCollisions): No query UUIDs or object IDs provided" << std::endl;
223 std::vector<uint> all_query_UUIDs = query_UUIDs;
225 for (
uint ObjID: query_object_IDs) {
227 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Query object ID " + std::to_string(ObjID) +
" does not exist");
231 all_query_UUIDs.insert(all_query_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
235 if (!validateUUIDs(all_query_UUIDs)) {
236 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid query UUIDs provided");
241 if (target_UUIDs.empty() && target_object_IDs.empty()) {
246 std::vector<uint> all_target_UUIDs = target_UUIDs;
248 for (
uint ObjID: target_object_IDs) {
250 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Target object ID " + std::to_string(ObjID) +
" does not exist");
254 all_target_UUIDs.insert(all_target_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
258 if (tree_based_bvh_enabled && allow_spatial_culling && !all_query_UUIDs.empty()) {
261 if (!all_query_UUIDs.empty()) {
265 query_center = (min_corner + max_corner) * 0.5f;
271 all_target_UUIDs = effective_targets;
275 if (!all_target_UUIDs.empty() && !validateUUIDs(all_target_UUIDs)) {
276 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid target UUIDs provided");
280 if (!all_target_UUIDs.empty()) {
286 std::vector<uint> all_collisions;
288 for (
uint UUID: all_query_UUIDs) {
294 vec3 aabb_min, aabb_max;
297 std::vector<uint> collisions;
299#ifdef HELIOS_CUDA_AVAILABLE
300 if (gpu_acceleration_enabled && gpu_memory_allocated) {
301 collisions = traverseBVH_GPU(aabb_min, aabb_max);
303 collisions = traverseBVH_CPU(aabb_min, aabb_max);
306 collisions = traverseBVH_CPU(aabb_min, aabb_max);
310 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
313 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
317 std::sort(all_collisions.begin(), all_collisions.end());
318 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
320 return all_collisions;
325 std::vector<uint> primitives_to_include;
331 primitives_to_include = UUIDs;
335 if (primitives_to_include.empty()) {
337 std::cerr <<
"WARNING (CollisionDetection::buildBVH): No primitives found to build BVH" << std::endl;
343 std::vector<uint> valid_primitives;
344 if (!UUIDs.empty()) {
346 for (
uint uuid: primitives_to_include) {
348 valid_primitives.push_back(uuid);
351 std::cerr <<
"ERROR (CollisionDetection::buildBVH): Invalid UUID " << uuid << std::endl;
353 helios_runtime_error(
"ERROR (CollisionDetection::buildBVH): Invalid UUID " + std::to_string(uuid) +
" provided");
358 for (
uint uuid: primitives_to_include) {
360 valid_primitives.push_back(uuid);
361 }
else if (printmessages) {
362 std::cerr <<
"WARNING (CollisionDetection::buildBVH): Skipping invalid UUID " << uuid << std::endl;
366 if (valid_primitives.empty()) {
368 std::cerr <<
"WARNING (CollisionDetection::buildBVH): No valid primitives found after filtering" << std::endl;
374 primitives_to_include = valid_primitives;
377 std::set<uint> new_primitive_set(primitives_to_include.begin(), primitives_to_include.end());
378 std::set<uint> old_primitive_set(primitive_indices.begin(), primitive_indices.end());
380 bool primitive_set_changed = (new_primitive_set != old_primitive_set);
382 if (primitive_set_changed) {
384 primitive_cache.clear();
389 primitive_indices.clear();
392 primitive_indices = primitives_to_include;
396 size_t max_nodes = std::max(
size_t(1), 2 * primitives_to_include.size());
398 bvh_nodes.resize(max_nodes);
399 next_available_node_index = 1;
403 std::unordered_set<uint> current_primitives(primitives_to_include.begin(), primitives_to_include.end());
406 auto cache_it = primitive_aabbs_cache.begin();
407 while (cache_it != primitive_aabbs_cache.end()) {
408 if (current_primitives.find(cache_it->first) == current_primitives.end()) {
409 cache_it = primitive_aabbs_cache.erase(cache_it);
416 for (
uint UUID: primitives_to_include) {
422 bool needs_update = (primitive_aabbs_cache.find(UUID) == primitive_aabbs_cache.end()) || (dirty_primitive_cache.find(UUID) != dirty_primitive_cache.end());
425 vec3 aabb_min, aabb_max;
427 primitive_aabbs_cache[UUID] = {aabb_min, aabb_max};
428 dirty_primitive_cache.erase(UUID);
433 buildBVHRecursive(0, 0, primitive_indices.size(), 0);
436 bvh_nodes.resize(next_available_node_index);
440 bvh_nodes_soa.clear();
441 bvh_nodes_soa.node_count = 0;
444#ifdef HELIOS_CUDA_AVAILABLE
445 if (gpu_acceleration_enabled) {
456 last_processed_uuids.clear();
457 last_processed_uuids.insert(primitives_to_include.begin(), primitives_to_include.end());
459 last_processed_deleted_uuids.clear();
460 last_processed_deleted_uuids.insert(context_deleted_uuids.begin(), context_deleted_uuids.end());
463 last_bvh_geometry.clear();
464 last_bvh_geometry.insert(primitives_to_include.begin(), primitives_to_include.end());
475 automatic_bvh_rebuilds =
false;
479 automatic_bvh_rebuilds =
true;
483 hierarchical_bvh_enabled =
true;
484 static_bvh_valid =
false;
488 hierarchical_bvh_enabled =
false;
490 static_bvh_nodes.clear();
491 static_bvh_primitives.clear();
492 static_bvh_valid =
false;
493 last_static_bvh_geometry.clear();
496void CollisionDetection::updateHierarchicalBVH(
const std::set<uint> &requested_geometry,
bool force_rebuild) {
499 if (!static_bvh_valid || force_rebuild || static_geometry_cache != last_static_bvh_geometry) {
504 std::vector<uint> dynamic_geometry;
505 for (
uint uuid: requested_geometry) {
506 if (static_geometry_cache.find(uuid) == static_geometry_cache.end()) {
507 dynamic_geometry.push_back(uuid);
513 if (!dynamic_geometry.empty()) {
518 primitive_indices.clear();
522 last_bvh_geometry = requested_geometry;
528 if (static_geometry_cache.empty()) {
529 static_bvh_nodes.clear();
530 static_bvh_primitives.clear();
531 static_bvh_valid =
false;
535 std::vector<uint> static_primitives(static_geometry_cache.begin(), static_geometry_cache.end());
540 std::vector<BVHNode> temp_nodes;
541 std::vector<uint> temp_primitives;
544 std::swap(bvh_nodes, temp_nodes);
545 std::swap(primitive_indices, temp_primitives);
551 static_bvh_nodes = bvh_nodes;
552 static_bvh_primitives = primitive_indices;
553 std::swap(bvh_nodes, temp_nodes);
554 std::swap(primitive_indices, temp_primitives);
556 static_bvh_valid =
true;
557 last_static_bvh_geometry = static_geometry_cache;
562 std::set<uint> requested_geometry(UUIDs.begin(), UUIDs.end());
565 bool geometry_changed = (requested_geometry != last_bvh_geometry) || bvh_dirty;
567 if (!geometry_changed && !force_rebuild) {
572 if (hierarchical_bvh_enabled) {
573 updateHierarchicalBVH(requested_geometry, force_rebuild);
578 if (force_rebuild || bvh_nodes.empty()) {
583 std::set<uint> added_geometry, removed_geometry;
586 std::set_difference(requested_geometry.begin(), requested_geometry.end(), last_bvh_geometry.begin(), last_bvh_geometry.end(), std::inserter(added_geometry, added_geometry.begin()));
589 std::set_difference(last_bvh_geometry.begin(), last_bvh_geometry.end(), requested_geometry.begin(), requested_geometry.end(), std::inserter(removed_geometry, removed_geometry.begin()));
592 size_t total_change = added_geometry.size() + removed_geometry.size();
593 size_t current_size = std::max(last_bvh_geometry.size(), requested_geometry.size());
597 bool mostly_additions = (removed_geometry.size() < added_geometry.size() * 0.1f);
598 float change_threshold = mostly_additions ? 0.5f : 0.2f;
600 if (current_size == 0 || (
float(total_change) /
float(current_size)) > change_threshold) {
604 incrementalUpdateBVH(added_geometry, removed_geometry, requested_geometry);
609 last_bvh_geometry = requested_geometry;
615 static_geometry_cache.clear();
616 static_geometry_cache.insert(UUIDs.begin(), UUIDs.end());
619void CollisionDetection::ensureBVHCurrent() {
621 if (!automatic_bvh_rebuilds) {
626 if (bvh_nodes.empty()) {
628 std::cout <<
"Building initial BVH..." << std::endl;
636 std::vector<uint> context_dirty_uuids = context->
getDirtyUUIDs(
false);
640 std::set<uint> current_dirty(context_dirty_uuids.begin(), context_dirty_uuids.end());
641 std::set<uint> current_deleted(context_deleted_uuids.begin(), context_deleted_uuids.end());
644 bool has_new_dirty =
false;
645 bool has_new_deleted =
false;
648 for (
uint uuid: current_dirty) {
649 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
650 has_new_dirty =
true;
656 for (
uint uuid: current_deleted) {
657 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
658 has_new_deleted =
true;
664 if (has_new_dirty || has_new_deleted) {
666 std::cout <<
"Geometry has changed since last BVH build, rebuilding..." << std::endl;
677 if (bvh_nodes.empty()) {
682 std::vector<uint> all_context_uuids = context->
getAllUUIDs();
683 for (
uint uuid: all_context_uuids) {
684 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
691 for (
uint uuid: context_deleted_uuids) {
692 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
698 for (
uint uuid: primitive_indices) {
708#ifdef HELIOS_CUDA_AVAILABLE
709 gpu_acceleration_enabled =
true;
710 if (!bvh_nodes.empty()) {
715 std::cerr <<
"WARNING: GPU acceleration requested but CUDA not available. Ignoring request." << std::endl;
721 gpu_acceleration_enabled =
false;
722#ifdef HELIOS_CUDA_AVAILABLE
728 return gpu_acceleration_enabled;
732 printmessages =
false;
736 printmessages =
true;
740 return primitive_indices.size();
745 node_count = bvh_nodes.size();
750 std::function<void(
uint,
size_t)> traverse = [&](
uint node_idx,
size_t depth) {
751 if (node_idx >= bvh_nodes.size())
754 const BVHNode &node = bvh_nodes[node_idx];
755 max_depth = std::max(max_depth, depth);
760 if (node.left_child != 0xFFFFFFFF) {
761 traverse(node.left_child, depth + 1);
763 if (node.right_child != 0xFFFFFFFF) {
764 traverse(node.right_child, depth + 1);
769 if (!bvh_nodes.empty()) {
774void CollisionDetection::calculateAABB(
const std::vector<uint> &primitives,
vec3 &aabb_min,
vec3 &aabb_max)
const {
776 if (primitives.empty()) {
783 size_t first_valid = 0;
784 while (first_valid < primitives.size() && !context->
doesPrimitiveExist(primitives[first_valid])) {
787 if (first_valid >= primitives.size()) {
798 for (
size_t i = first_valid + 1; i < primitives.size(); i++) {
802 vec3 prim_min, prim_max;
805 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
806 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
807 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
809 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
810 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
811 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
815void CollisionDetection::buildBVHRecursive(
uint node_index,
size_t primitive_start,
size_t primitive_count,
int depth) {
818 if (node_index >= bvh_nodes.size()) {
819 throw std::runtime_error(
"CollisionDetection: BVH recursive access exceeded pre-allocated capacity");
823 if (primitive_start + primitive_count > primitive_indices.size()) {
824 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(" +
825 std::to_string(primitive_indices.size()) +
")");
828 BVHNode &node = bvh_nodes[node_index];
831 if (primitive_count == 0) {
836 uint first_uuid = primitive_indices[primitive_start];
837 auto it = primitive_aabbs_cache.find(first_uuid);
838 if (it == primitive_aabbs_cache.end()) {
844 const auto &first_cached_aabb = it->second;
845 node.aabb_min = first_cached_aabb.first;
846 node.aabb_max = first_cached_aabb.second;
849 for (
size_t i = 1; i < primitive_count; i++) {
850 uint uuid = primitive_indices[primitive_start + i];
851 auto it = primitive_aabbs_cache.find(uuid);
852 if (it == primitive_aabbs_cache.end()) {
855 const auto &cached_aabb = it->second;
856 node.aabb_min.x = std::min(node.aabb_min.x, cached_aabb.first.x);
857 node.aabb_min.y = std::min(node.aabb_min.y, cached_aabb.first.y);
858 node.aabb_min.z = std::min(node.aabb_min.z, cached_aabb.first.z);
860 node.aabb_max.x = std::max(node.aabb_max.x, cached_aabb.second.x);
861 node.aabb_max.y = std::max(node.aabb_max.y, cached_aabb.second.y);
862 node.aabb_max.z = std::max(node.aabb_max.z, cached_aabb.second.z);
868 const int MAX_PRIMITIVES_PER_LEAF = (primitive_indices.size() > 500000) ? 500 : 100;
869 const int MAX_DEPTH = (primitive_indices.size() > 500000) ? 6 : 10;
871 if (primitive_count <= MAX_PRIMITIVES_PER_LEAF || depth >= MAX_DEPTH) {
874 node.primitive_start = primitive_start;
875 node.primitive_count = primitive_count;
876 node.left_child = 0xFFFFFFFF;
877 node.right_child = 0xFFFFFFFF;
882 vec3 extent = node.aabb_max - node.aabb_min;
884 if (extent.
y > extent.
x)
886 if (extent.
z > (split_axis == 0 ? extent.
x : extent.y))
891 std::sort(primitive_indices.begin() + primitive_start, primitive_indices.begin() + primitive_start + primitive_count, [&](
uint a,
uint b) {
893 auto it_a = primitive_aabbs_cache.find(a);
894 auto it_b = primitive_aabbs_cache.find(b);
895 if (it_a == primitive_aabbs_cache.end() || it_b == primitive_aabbs_cache.end()) {
899 const auto &aabb_a = it_a->second;
900 const auto &aabb_b = it_b->second;
903 float centroid_a_coord, centroid_b_coord;
904 if (split_axis == 0) {
905 centroid_a_coord = 0.5f * (aabb_a.first.x + aabb_a.second.x);
906 centroid_b_coord = 0.5f * (aabb_b.first.x + aabb_b.second.x);
907 }
else if (split_axis == 1) {
908 centroid_a_coord = 0.5f * (aabb_a.first.y + aabb_a.second.y);
909 centroid_b_coord = 0.5f * (aabb_b.first.y + aabb_b.second.y);
911 centroid_a_coord = 0.5f * (aabb_a.first.z + aabb_a.second.z);
912 centroid_b_coord = 0.5f * (aabb_b.first.z + aabb_b.second.z);
915 return centroid_a_coord < centroid_b_coord;
919 size_t split_index = primitive_count / 2;
922 uint left_child_index = next_available_node_index++;
923 uint right_child_index = next_available_node_index++;
926 if (right_child_index >= bvh_nodes.size()) {
927 throw std::runtime_error(
"CollisionDetection: BVH node allocation exceeded pre-calculated capacity");
931 BVHNode &updated_node = bvh_nodes[node_index];
932 updated_node.left_child = left_child_index;
933 updated_node.right_child = right_child_index;
934 updated_node.is_leaf =
false;
935 updated_node.primitive_start = 0;
936 updated_node.primitive_count = 0;
939 buildBVHRecursive(left_child_index, primitive_start, split_index, depth + 1);
940 buildBVHRecursive(right_child_index, primitive_start + split_index, primitive_count - split_index, depth + 1);
943std::vector<uint> CollisionDetection::traverseBVH_CPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
945 std::vector<uint> results;
947 if (bvh_nodes.empty()) {
952 std::vector<uint> node_stack;
953 node_stack.push_back(0);
955 while (!node_stack.empty()) {
956 uint node_idx = node_stack.back();
957 node_stack.pop_back();
959 if (node_idx >= bvh_nodes.size())
962 const BVHNode &node = bvh_nodes[node_idx];
965 if (!aabbIntersect(query_aabb_min, query_aabb_max, node.aabb_min, node.aabb_max)) {
971 for (
uint i = 0; i < node.primitive_count; i++) {
972 uint primitive_id = primitive_indices[node.primitive_start + i];
978 vec3 prim_min, prim_max;
982 if (aabbIntersect(query_aabb_min, query_aabb_max, prim_min, prim_max)) {
983 results.push_back(primitive_id);
988 if (node.left_child != 0xFFFFFFFF) {
989 node_stack.push_back(node.left_child);
991 if (node.right_child != 0xFFFFFFFF) {
992 node_stack.push_back(node.right_child);
1000#ifdef HELIOS_CUDA_AVAILABLE
1001std::vector<uint> CollisionDetection::traverseBVH_GPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
1002 if (!gpu_memory_allocated) {
1003 helios_runtime_error(
"ERROR: GPU traversal requested but GPU memory is not allocated. Call buildBVH() or transferBVHToGPU() first.");
1007 float query_min_array[3] = {query_aabb_min.
x, query_aabb_min.
y, query_aabb_min.
z};
1008 float query_max_array[3] = {query_aabb_max.
x, query_aabb_max.
y, query_aabb_max.
z};
1011 std::vector<float> primitive_min_array(primitive_indices.size() * 3);
1012 std::vector<float> primitive_max_array(primitive_indices.size() * 3);
1014 for (
size_t i = 0; i < primitive_indices.size(); i++) {
1015 uint uuid = primitive_indices[i];
1016 auto it = primitive_aabbs_cache.find(uuid);
1017 if (it == primitive_aabbs_cache.end()) {
1020 const auto &cached_aabb = it->second;
1022 primitive_min_array[i * 3] = cached_aabb.first.x;
1023 primitive_min_array[i * 3 + 1] = cached_aabb.first.y;
1024 primitive_min_array[i * 3 + 2] = cached_aabb.first.z;
1026 primitive_max_array[i * 3] = cached_aabb.second.x;
1027 primitive_max_array[i * 3 + 1] = cached_aabb.second.y;
1028 primitive_max_array[i * 3 + 2] = cached_aabb.second.z;
1031 const int max_results = 1000;
1032 std::vector<unsigned int> results(max_results);
1033 unsigned int result_count = 0;
1036 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);
1039 std::vector<uint> final_results;
1040 for (
unsigned int i = 0; i < result_count; i++) {
1041 final_results.push_back(results[i]);
1044 return final_results;
1048bool CollisionDetection::aabbIntersect(
const vec3 &min1,
const vec3 &max1,
const vec3 &min2,
const vec3 &max2) {
1049 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);
1052bool CollisionDetection::rayAABBIntersect(
const vec3 &origin,
const vec3 &direction,
const vec3 &aabb_min,
const vec3 &aabb_max,
float &t_min,
float &t_max)
const {
1055 t_max = std::numeric_limits<float>::max();
1058 for (
int i = 0; i < 3; i++) {
1059 float dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
1060 float orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
1061 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1062 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1064 if (std::abs(dir_component) < 1e-9f) {
1066 if (orig_component < min_component || orig_component > max_component) {
1071 float t1 = (min_component - orig_component) / dir_component;
1072 float t2 = (max_component - orig_component) / dir_component;
1080 t_min = std::max(t_min, t1);
1081 t_max = std::min(t_max, t2);
1084 if (t_min > t_max) {
1091 return t_max >= 0.0f;
1094bool CollisionDetection::coneAABBIntersect(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1096 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) {
1101 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1102 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1103 float box_radius = box_half_extents.
magnitude();
1106 if (cone.height <= 0.0f) {
1108 for (
int i = 0; i < 8; i++) {
1109 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);
1112 vec3 apex_to_corner = corner - cone.apex;
1113 float distance_along_axis = apex_to_corner * cone.axis;
1116 if (distance_along_axis > 0) {
1118 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1119 if (cos_angle >= cosf(cone.half_angle)) {
1128 float t_max = std::numeric_limits<float>::max();
1130 for (
int i = 0; i < 3; i++) {
1131 float axis_component = (i == 0) ? cone.axis.x : (i == 1) ? cone.axis.y : cone.axis.z;
1132 float apex_component = (i == 0) ? cone.apex.x : (i == 1) ? cone.apex.y : cone.apex.z;
1133 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1134 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1136 if (std::abs(axis_component) < 1e-6f) {
1138 if (apex_component < min_component || apex_component > max_component) {
1143 float t1 = (min_component - apex_component) / axis_component;
1144 float t2 = (max_component - apex_component) / axis_component;
1149 t_min = std::max(t_min, t1);
1150 t_max = std::min(t_max, t2);
1152 if (t_min > t_max) {
1160 if (t_min >= 0 && t_max >= 0) {
1163 float t_check = std::max(0.0f, t_min);
1164 vec3 axis_point = cone.apex + cone.axis * t_check;
1167 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)));
1170 vec3 apex_to_point = closest_in_box - cone.apex;
1171 float distance_along_axis = apex_to_point * cone.axis;
1173 if (distance_along_axis > 0) {
1174 float distance_to_point = apex_to_point.
magnitude();
1175 if (distance_to_point > 0) {
1176 float cos_angle = distance_along_axis / distance_to_point;
1177 if (cos_angle >= cosf(cone.half_angle)) {
1186 for (
int i = 0; i < 8; i++) {
1187 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);
1190 vec3 apex_to_corner = corner - cone.apex;
1191 float distance_along_axis = apex_to_corner * cone.axis;
1194 if (distance_along_axis > 0 && distance_along_axis <= cone.height) {
1196 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1197 if (cos_angle >= cosf(cone.half_angle)) {
1204 vec3 base_center = cone.apex + cone.axis * cone.height;
1205 float base_radius = cone.height * tanf(cone.half_angle);
1208 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)));
1210 float dist_sq = (closest_point - base_center).magnitude();
1211 if (dist_sq <= base_radius) {
1221bool CollisionDetection::coneAABBIntersectFast(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1226 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) {
1231 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1232 vec3 apex_to_center = box_center - cone.apex;
1233 float distance_along_axis = apex_to_center * cone.axis;
1235 if (distance_along_axis <= 0.0f) {
1240 if (cone.height > 0.0f && distance_along_axis > cone.height) {
1242 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1243 float box_radius = box_half_extents.
magnitude();
1244 if (distance_along_axis - box_radius > cone.height) {
1250 float max_distance = (cone.height > 0.0f) ? cone.height : distance_along_axis;
1251 float max_radius_at_distance = max_distance * tanf(cone.half_angle);
1254 vec3 axis_point = cone.apex + cone.axis * distance_along_axis;
1255 float distance_from_axis = (box_center - axis_point).magnitude();
1258 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1259 float box_radius = box_half_extents.
magnitude();
1261 if (distance_from_axis > max_radius_at_distance + box_radius) {
1267 return coneAABBIntersect(cone, aabb_min, aabb_max);
1272int CollisionDetection::calculateOptimalBinCount(
float cone_half_angle,
int geometry_count) {
1274 float base_resolution =
M_PI / 180.0f;
1275 float cone_solid_angle = 2.0f *
M_PI * (1.0f - cosf(cone_half_angle));
1276 int optimal_bins = (int) (cone_solid_angle / (base_resolution * base_resolution));
1280 int max_bins = std::min(1024, geometry_count * 4);
1282 return std::clamp(optimal_bins, min_bins, max_bins);
1285std::vector<uint> CollisionDetection::filterPrimitivesParallel(
const Cone &cone,
const std::vector<uint> &primitive_uuids) {
1286 std::vector<uint> filtered_uuids;
1288 if (primitive_uuids.empty()) {
1289 return filtered_uuids;
1293 filtered_uuids.reserve(primitive_uuids.size() / 10);
1297 const int num_threads = omp_get_max_threads();
1298 std::vector<std::vector<uint>> thread_results(num_threads);
1301 for (
int i = 0; i < num_threads; i++) {
1302 thread_results[i].reserve(primitive_uuids.size() / (num_threads * 10));
1308 int thread_id = omp_get_thread_num();
1309 std::vector<uint> &local_results = thread_results[thread_id];
1311#pragma omp for nowait
1312 for (
int i = 0; i < static_cast<int>(primitive_uuids.size()); i++) {
1313 uint uuid = primitive_uuids[i];
1318 if (!vertices.empty()) {
1320 vec3 aabb_min = vertices[0];
1321 vec3 aabb_max = vertices[0];
1322 for (
const vec3 &vertex: vertices) {
1323 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));
1324 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));
1328 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1329 local_results.push_back(uuid);
1337 size_t total_count = 0;
1338 for (
const auto &thread_result: thread_results) {
1339 total_count += thread_result.size();
1342 filtered_uuids.reserve(total_count);
1343 for (
const auto &thread_result: thread_results) {
1344 filtered_uuids.insert(filtered_uuids.end(), thread_result.begin(), thread_result.end());
1348 for (
size_t i = 0; i < primitive_uuids.size(); i++) {
1349 uint uuid = primitive_uuids[i];
1354 if (!vertices.empty()) {
1356 vec3 aabb_min = vertices[0];
1357 vec3 aabb_max = vertices[0];
1358 for (
const vec3 &vertex: vertices) {
1359 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));
1360 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));
1364 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1365 filtered_uuids.push_back(uuid);
1372 return filtered_uuids;
1375float CollisionDetection::cartesianToSphericalCone(
const vec3 &vector,
const vec3 &cone_axis,
float &theta,
float &phi) {
1377 if (distance < 1e-6f) {
1383 vec3 normalized_vector = vector / distance;
1386 float cos_phi = normalized_vector * cone_axis;
1387 phi = acosf(std::clamp(cos_phi, -1.0f, 1.0f));
1397 vec3 projected = normalized_vector - cone_axis * cos_phi;
1400 float cos_theta = projected * right;
1401 float sin_theta = projected * forward;
1402 theta = atan2f(sin_theta, cos_theta);
1404 theta += 2.0f *
M_PI;
1412bool CollisionDetection::sphericalCoordsToBinIndices(
float theta,
float phi,
const AngularBins &bins,
int &theta_bin,
int &phi_bin) {
1414 if (phi < 0.0f || theta < 0.0f || theta >= 2.0f *
M_PI) {
1419 theta_bin = (int) (theta * bins.theta_divisions / (2.0f *
M_PI));
1420 phi_bin = (int) (phi * bins.phi_divisions /
M_PI);
1423 theta_bin = std::clamp(theta_bin, 0, bins.theta_divisions - 1);
1424 phi_bin = std::clamp(phi_bin, 0, bins.phi_divisions - 1);
1429void CollisionDetection::projectGeometryToBins(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1432 const int PARALLEL_THRESHOLD = 500;
1434 if (filtered_uuids.size() > PARALLEL_THRESHOLD) {
1437 const int num_threads = omp_get_max_threads();
1438 std::vector<AngularBins> thread_bins(num_threads, AngularBins(bins.theta_divisions, bins.phi_divisions));
1442 int thread_id = omp_get_thread_num();
1443 AngularBins &local_bins = thread_bins[thread_id];
1445#pragma omp for nowait
1446 for (
int i = 0; i < static_cast<int>(filtered_uuids.size()); i++) {
1447 uint uuid = filtered_uuids[i];
1453 for (
const vec3 &vertex: vertices) {
1454 vec3 apex_to_vertex = vertex - cone.apex;
1455 float distance = apex_to_vertex.
magnitude();
1457 if (distance > 1e-6f) {
1459 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1462 if (phi <= cone.half_angle) {
1463 int theta_bin, phi_bin;
1464 if (sphericalCoordsToBinIndices(theta, phi, local_bins, theta_bin, phi_bin)) {
1465 local_bins.setCovered(theta_bin, phi_bin, distance);
1475 for (
const auto &thread_bin: thread_bins) {
1476 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1477 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1478 if (thread_bin.isCovered(theta, phi)) {
1479 int index = theta * bins.phi_divisions + phi;
1480 float thread_depth = thread_bin.depth_values[index];
1481 bins.setCovered(theta, phi, thread_depth);
1488 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1492 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1496void CollisionDetection::projectGeometryToBinsSerial(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1497 for (
uint uuid: filtered_uuids) {
1502 for (
const vec3 &vertex: vertices) {
1503 vec3 apex_to_vertex = vertex - cone.apex;
1504 float distance = apex_to_vertex.
magnitude();
1506 if (distance > 1e-6f) {
1508 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1511 if (phi <= cone.half_angle) {
1512 int theta_bin, phi_bin;
1513 if (sphericalCoordsToBinIndices(theta, phi, bins, theta_bin, phi_bin)) {
1514 bins.setCovered(theta_bin, phi_bin, distance);
1523std::vector<CollisionDetection::Gap> CollisionDetection::findGapsInCoverageMap(
const AngularBins &bins,
const Cone &cone) {
1524 std::vector<Gap> gaps;
1527 std::vector<std::vector<bool>> visited(bins.theta_divisions, std::vector<bool>(bins.phi_divisions,
false));
1529 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1530 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1531 if (!bins.isCovered(theta, phi) && !visited[theta][phi]) {
1533 Gap gap = floodFillGap(bins, theta, phi, visited, cone);
1536 const float MIN_GAP_SIZE_STERADIANS = 0.01f;
1537 if (gap.angular_size > MIN_GAP_SIZE_STERADIANS) {
1538 gaps.push_back(gap);
1545 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.angular_size > b.angular_size; });
1550CollisionDetection::Gap CollisionDetection::floodFillGap(
const AngularBins &bins,
int start_theta,
int start_phi, std::vector<std::vector<bool>> &visited,
const Cone &cone) {
1552 std::queue<std::pair<int, int>> queue;
1553 queue.push({start_theta, start_phi});
1555 float total_solid_angle = 0;
1556 vec3 weighted_center(0, 0, 0);
1558 while (!queue.empty()) {
1559 auto [theta, phi] = queue.front();
1562 if (visited[theta][phi] || bins.isCovered(theta, phi))
1564 visited[theta][phi] =
true;
1567 float bin_solid_angle = calculateBinSolidAngle(theta, phi, bins, cone.half_angle);
1568 total_solid_angle += bin_solid_angle;
1571 vec3 bin_direction = binIndicesToCartesian(theta, phi, bins, cone);
1572 weighted_center = weighted_center + bin_direction * bin_solid_angle;
1575 addUnoccupiedNeighbors(theta, phi, bins, visited, queue);
1578 gap.angular_size = total_solid_angle;
1579 gap.center_direction = weighted_center.magnitude() > 1e-6f ? weighted_center.normalize() : cone.axis;
1584float CollisionDetection::calculateBinSolidAngle(
int theta_bin,
int phi_bin,
const AngularBins &bins,
float cone_half_angle) {
1586 float theta_step = 2.0f *
M_PI / bins.theta_divisions;
1587 float phi_step = cone_half_angle / bins.phi_divisions;
1590 float phi = (phi_bin + 0.5f) * phi_step;
1591 return theta_step * phi_step * sinf(phi);
1594vec3 CollisionDetection::binIndicesToCartesian(
int theta_bin,
int phi_bin,
const AngularBins &bins,
const Cone &cone) {
1596 float theta = (theta_bin + 0.5f) * 2.0f *
M_PI / bins.theta_divisions;
1597 float phi = (phi_bin + 0.5f) * cone.half_angle / bins.phi_divisions;
1606 float sin_phi = sinf(phi);
1607 float cos_phi = cosf(phi);
1608 float sin_theta = sinf(theta);
1609 float cos_theta = cosf(theta);
1611 vec3 direction = cone.axis * cos_phi + (right * cos_theta + forward * sin_theta) * sin_phi;
1615void CollisionDetection::addUnoccupiedNeighbors(
int theta,
int phi,
const AngularBins &bins, std::vector<std::vector<bool>> &visited, std::queue<std::pair<int, int>> &queue) {
1617 const int neighbors[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
1619 for (
int i = 0; i < 4; i++) {
1620 int new_theta = theta + neighbors[i][0];
1621 int new_phi = phi + neighbors[i][1];
1625 new_theta += bins.theta_divisions;
1626 if (new_theta >= bins.theta_divisions)
1627 new_theta -= bins.theta_divisions;
1630 if (new_phi >= 0 && new_phi < bins.phi_divisions) {
1631 if (!visited[new_theta][new_phi] && !bins.isCovered(new_theta, new_phi)) {
1632 queue.push({new_theta, new_phi});
1649 if (initialSamples <= 0 || half_angle <= 0.0f || half_angle >
M_PI) {
1650 if (printmessages) {
1651 std::cerr <<
"WARNING: Invalid parameters for findOptimalConePath" << std::endl;
1656 if (bvh_nodes.empty()) {
1663 std::vector<Gap> detected_gaps = detectGapsInCone(apex, centralAxis, half_angle, height, initialSamples);
1665 if (detected_gaps.empty()) {
1675 scoreGapsByFishEyeMetric(detected_gaps, centralAxis);
1678 result.
direction = findOptimalGapDirection(detected_gaps, centralAxis);
1681 float max_distance = (height > 0.0f) ? height : -1.0f;
1686 if (!detected_gaps.empty()) {
1688 const Gap &best_gap = detected_gaps[0];
1689 result.
confidence = std::min(1.0f, best_gap.angular_size * 10.0f);
1695#ifdef HELIOS_CUDA_AVAILABLE
1696void CollisionDetection::allocateGPUMemory() {
1697 if (gpu_memory_allocated) {
1701 if (bvh_nodes.empty() || primitive_indices.empty()) {
1706 d_bvh_nodes =
nullptr;
1707 d_primitive_indices =
nullptr;
1710 size_t bvh_size = bvh_nodes.size() *
sizeof(
GPUBVHNode);
1711 size_t indices_size = primitive_indices.size() *
sizeof(
uint);
1714 if (bvh_size == 0 || indices_size == 0) {
1719 cudaError_t err = cudaMalloc(&d_bvh_nodes, bvh_size);
1720 if (err != cudaSuccess) {
1721 helios_runtime_error(
"CUDA error allocating BVH nodes: " + std::string(cudaGetErrorString(err)));
1725 err = cudaMalloc((
void **) &d_primitive_indices, indices_size);
1726 if (err != cudaSuccess) {
1727 cudaFree(d_bvh_nodes);
1728 d_bvh_nodes =
nullptr;
1729 helios_runtime_error(
"CUDA error allocating primitive indices: " + std::string(cudaGetErrorString(err)));
1733 gpu_memory_allocated =
true;
1737#ifdef HELIOS_CUDA_AVAILABLE
1738void CollisionDetection::freeGPUMemory() {
1739 if (!gpu_memory_allocated)
1743 cudaFree(d_bvh_nodes);
1744 d_bvh_nodes =
nullptr;
1747 if (d_primitive_indices) {
1748 cudaFree(d_primitive_indices);
1749 d_primitive_indices =
nullptr;
1752 gpu_memory_allocated =
false;
1756#ifdef HELIOS_CUDA_AVAILABLE
1757void CollisionDetection::transferBVHToGPU() {
1758 if (!gpu_acceleration_enabled || bvh_nodes.empty()) {
1763 if (gpu_memory_allocated) {
1766 allocateGPUMemory();
1769 if (!gpu_memory_allocated || d_bvh_nodes ==
nullptr || d_primitive_indices ==
nullptr) {
1774 std::vector<GPUBVHNode> gpu_nodes(bvh_nodes.size());
1775 for (
size_t i = 0; i < bvh_nodes.size(); i++) {
1776 const BVHNode &cpu_node = bvh_nodes[i];
1779 gpu_node.
aabb_min = heliosVecToFloat3(cpu_node.aabb_min);
1780 gpu_node.
aabb_max = heliosVecToFloat3(cpu_node.aabb_max);
1785 gpu_node.
is_leaf = cpu_node.is_leaf ? 1 : 0;
1790 cudaError_t err = cudaMemcpy(d_bvh_nodes, gpu_nodes.data(), gpu_nodes.size() *
sizeof(
GPUBVHNode), cudaMemcpyHostToDevice);
1791 if (err != cudaSuccess) {
1792 helios_runtime_error(
"CUDA error transferring BVH nodes: " + std::string(cudaGetErrorString(err)));
1795 err = cudaMemcpy(d_primitive_indices, primitive_indices.data(), primitive_indices.size() *
sizeof(
uint), cudaMemcpyHostToDevice);
1796 if (err != cudaSuccess) {
1797 helios_runtime_error(
"CUDA error transferring primitive indices: " + std::string(cudaGetErrorString(err)));
1802void CollisionDetection::markBVHDirty() {
1804 last_processed_uuids.clear();
1805 last_processed_deleted_uuids.clear();
1806 last_bvh_geometry.clear();
1813#ifdef HELIOS_CUDA_AVAILABLE
1818void CollisionDetection::incrementalUpdateBVH(
const std::set<uint> &added_geometry,
const std::set<uint> &removed_geometry,
const std::set<uint> &final_geometry) {
1821 for (
uint uuid: added_geometry) {
1823 if (printmessages) {
1824 std::cerr <<
"Warning: Added primitive " << uuid <<
" does not exist, falling back to full rebuild" << std::endl;
1826 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
1836 for (
uint uuid: added_geometry) {
1837 updatePrimitiveAABBCache(uuid);
1841 for (
uint uuid: removed_geometry) {
1842 primitive_aabbs_cache.erase(uuid);
1849 size_t total_changes = added_geometry.size() + removed_geometry.size();
1850 size_t current_size = final_geometry.size();
1853 if (current_size > 0 && !bvh_nodes.empty() && (
float(total_changes) /
float(current_size)) < 0.05f) {
1855 bool insertion_successful =
true;
1858 if (!removed_geometry.empty()) {
1859 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());
1863 for (
uint uuid: added_geometry) {
1864 primitive_indices.push_back(uuid);
1869 if (insertion_successful && total_changes < 50) {
1870 if (printmessages) {
1871 std::cout <<
"Using targeted tree update for " << total_changes <<
" changes" << std::endl;
1877 optimizedRebuildBVH(final_geometry);
1883 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
1887 last_bvh_geometry = final_geometry;
1892bool CollisionDetection::validateUUIDs(
const std::vector<uint> &UUIDs)
const {
1893 bool all_valid =
true;
1894 for (
uint UUID: UUIDs) {
1896 if (printmessages) {
1897 std::cerr <<
"WARNING (CollisionDetection::validateUUIDs): Primitive UUID " + std::to_string(UUID) +
" does not exist - skipping" << std::endl;
1905bool CollisionDetection::rayPrimitiveIntersection(
const vec3 &origin,
const vec3 &direction,
uint primitive_UUID,
float &distance)
const {
1916 if (vertices.empty()) {
1922 float min_distance = std::numeric_limits<float>::max();
1924 if (type == PRIMITIVE_TYPE_TRIANGLE) {
1926 if (vertices.size() >= 3) {
1927 const vec3 &v0 = vertices[0];
1928 const vec3 &v1 = vertices[1];
1929 const vec3 &v2 = vertices[2];
1932 float a = v0.
x - v1.
x, b = v0.
x - v2.
x, c = direction.
x, d = v0.
x - origin.
x;
1933 float e = v0.
y - v1.
y, f = v0.
y - v2.
y, g = direction.
y, h = v0.
y - origin.
y;
1934 float i = v0.
z - v1.
z, j = v0.
z - v2.
z, k = direction.
z, l = v0.
z - origin.
z;
1936 float m = f * k - g * j, n = h * k - g * l, p = f * l - h * j;
1937 float q = g * i - e * k, s = e * j - f * i;
1939 float denom = a * m + b * q + c * s;
1940 if (std::abs(denom) < 1e-8f) {
1944 float inv_denom = 1.0f / denom;
1946 float e1 = d * m - b * n - c * p;
1947 float beta = e1 * inv_denom;
1950 float r = e * l - h * i;
1951 float e2 = a * n + d * q + c * r;
1952 float gamma = e2 * inv_denom;
1954 if (gamma >= 0.0f && beta + gamma <= 1.0f) {
1955 float e3 = a * p - b * r + d * s;
1956 float t = e3 * inv_denom;
1958 if (t > 1e-8f && t < min_distance) {
1965 }
else if (type == PRIMITIVE_TYPE_PATCH) {
1967 if (vertices.size() >= 4) {
1968 const vec3 &v0 = vertices[0];
1969 const vec3 &v1 = vertices[1];
1970 const vec3 &v2 = vertices[2];
1971 const vec3 &v3 = vertices[3];
1982 float denom = direction * normal;
1983 if (std::abs(denom) > 1e-8f) {
1984 float t = (anchor - origin) * normal / denom;
1986 if (t > 1e-8f && t < 1e8f) {
1988 vec3 p = origin + direction * t;
1989 vec3 d = p - anchor;
1992 float ddota = d * a;
1993 float ddotb = d * b;
1996 if (ddota >= 0.0f && ddota <= (a * a) && ddotb >= 0.0f && ddotb <= (b * b)) {
1998 if (t < min_distance) {
2006 }
else if (type == PRIMITIVE_TYPE_VOXEL) {
2008 if (vertices.size() == 8) {
2010 vec3 aabb_min = vertices[0];
2011 vec3 aabb_max = vertices[0];
2013 for (
int i = 1; i < 8; i++) {
2014 aabb_min.
x = std::min(aabb_min.
x, vertices[i].x);
2015 aabb_min.
y = std::min(aabb_min.
y, vertices[i].y);
2016 aabb_min.
z = std::min(aabb_min.
z, vertices[i].z);
2017 aabb_max.
x = std::max(aabb_max.
x, vertices[i].x);
2018 aabb_max.
y = std::max(aabb_max.
y, vertices[i].y);
2019 aabb_max.
z = std::max(aabb_max.
z, vertices[i].z);
2023 float t_near = -std::numeric_limits<float>::max();
2024 float t_far = std::numeric_limits<float>::max();
2027 for (
int i = 0; i < 3; i++) {
2028 float ray_dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
2029 float ray_orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
2030 float aabb_min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
2031 float aabb_max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
2033 if (std::abs(ray_dir_component) < 1e-8f) {
2035 if (ray_orig_component < aabb_min_component || ray_orig_component > aabb_max_component) {
2040 float t1 = (aabb_min_component - ray_orig_component) / ray_dir_component;
2041 float t2 = (aabb_max_component - ray_orig_component) / ray_dir_component;
2049 t_near = std::max(t_near, t1);
2050 t_far = std::min(t_far, t2);
2053 if (t_near > t_far) {
2060 if (t_far >= 0.0f && t_near < min_distance) {
2062 float intersection_distance = (t_near >= 1e-8f) ? t_near : t_far;
2063 if (intersection_distance >= 1e-8f) {
2064 min_distance = intersection_distance;
2072 distance = min_distance;
2077 }
catch (
const std::exception &e) {
2084 if (printmessages) {
2085 std::cerr <<
"WARNING: calculateGridIntersection not yet implemented" << std::endl;
2090 if (printmessages) {
2091 std::cerr <<
"WARNING: getGridCells not yet implemented" << std::endl;
2097 if (printmessages) {
2098 std::cerr <<
"WARNING: getGridIntersections not yet implemented" << std::endl;
2104 if (printmessages) {
2105 std::cerr <<
"WARNING: optimizeLayout not yet implemented" << std::endl;
2110int CollisionDetection::countRayIntersections(
const vec3 &origin,
const vec3 &direction,
float max_distance) {
2112 int intersection_count = 0;
2114 if (bvh_nodes.empty()) {
2115 return intersection_count;
2120 float min_distance = 0.05f;
2126 std::vector<uint> node_stack;
2127 node_stack.push_back(0);
2129 while (!node_stack.empty()) {
2130 uint node_idx = node_stack.back();
2131 node_stack.pop_back();
2133 if (node_idx >= bvh_nodes.size())
2136 const BVHNode &node = bvh_nodes[node_idx];
2140 if (!rayAABBIntersect(origin, direction, node.aabb_min, node.aabb_max, t_min, t_max)) {
2145 if (t_max < min_distance) {
2148 if (max_distance > 0.0f && t_min > max_distance) {
2154 for (
uint i = 0; i < node.primitive_count; i++) {
2155 uint primitive_id = primitive_indices[node.primitive_start + i];
2161 vec3 prim_min, prim_max;
2165 float prim_t_min, prim_t_max;
2166 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2168 bool within_min_distance = prim_t_min >= min_distance;
2169 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2171 if (within_min_distance && within_max_distance) {
2172 intersection_count++;
2178 if (node.left_child != 0xFFFFFFFF) {
2179 node_stack.push_back(node.left_child);
2181 if (node.right_child != 0xFFFFFFFF) {
2182 node_stack.push_back(node.right_child);
2187 return intersection_count;
2190bool CollisionDetection::findNearestRayIntersection(
const vec3 &origin,
const vec3 &direction,
const std::set<uint> &candidate_UUIDs,
float &nearest_distance,
float max_distance) {
2192 nearest_distance = std::numeric_limits<float>::max();
2193 bool found_intersection =
false;
2196 bool check_static_bvh = hierarchical_bvh_enabled && static_bvh_valid && !static_bvh_nodes.empty();
2197 bool check_dynamic_bvh = !bvh_nodes.empty();
2199 if (!check_static_bvh && !check_dynamic_bvh) {
2208 auto traverseBVH = [&](
const std::vector<BVHNode> &nodes,
const std::vector<uint> &primitives,
const char *bvh_name) {
2213 std::vector<uint> node_stack;
2214 node_stack.push_back(0);
2216 while (!node_stack.empty()) {
2217 uint node_idx = node_stack.back();
2218 node_stack.pop_back();
2220 if (node_idx >= nodes.size()) {
2224 const BVHNode &node = nodes[node_idx];
2228 if (!rayAABBIntersect(origin, direction, node.aabb_min, node.aabb_max, t_min, t_max)) {
2233 if (max_distance > 0.0f && t_min > max_distance) {
2238 if (t_min > nearest_distance) {
2244 for (
uint i = 0; i < node.primitive_count; i++) {
2245 uint primitive_id = primitives[node.primitive_start + i];
2249 if (!candidate_UUIDs.empty() && candidate_UUIDs.find(primitive_id) == candidate_UUIDs.end()) {
2259 vec3 prim_min, prim_max;
2263 float prim_t_min, prim_t_max;
2264 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2266 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2268 if (within_max_distance && prim_t_min > 0.0f && prim_t_min < nearest_distance) {
2271 nearest_distance = prim_t_min;
2272 found_intersection =
true;
2278 if (node.left_child != 0xFFFFFFFF) {
2279 node_stack.push_back(node.left_child);
2281 if (node.right_child != 0xFFFFFFFF) {
2282 node_stack.push_back(node.right_child);
2289 if (check_static_bvh) {
2290 traverseBVH(static_bvh_nodes, static_bvh_primitives,
"static");
2294 if (check_dynamic_bvh) {
2295 traverseBVH(bvh_nodes, primitive_indices,
"dynamic");
2298 return found_intersection;
2303 if (candidate_UUIDs.empty()) {
2304 if (printmessages) {
2305 std::cerr <<
"WARNING (CollisionDetection::findNearestPrimitiveDistance): No candidate UUIDs provided" << std::endl;
2311 float dir_magnitude = direction.
magnitude();
2312 if (std::abs(dir_magnitude - 1.0f) > 1e-6f) {
2313 if (printmessages) {
2314 std::cerr <<
"WARNING (CollisionDetection::findNearestPrimitiveDistance): Direction vector is not normalized (magnitude = " << dir_magnitude <<
")" << std::endl;
2321 std::vector<uint> valid_candidates;
2322 for (
uint uuid: candidate_UUIDs) {
2324 valid_candidates.push_back(uuid);
2325 }
else if (printmessages) {
2326 std::cerr <<
"WARNING (CollisionDetection::findNearestPrimitiveDistance): Skipping invalid UUID " << uuid << std::endl;
2331 if (valid_candidates.empty()) {
2332 if (printmessages) {
2333 std::cerr <<
"WARNING (CollisionDetection::findNearestPrimitiveDistance): No valid candidate UUIDs after filtering" << std::endl;
2338 float nearest_distance_found = std::numeric_limits<float>::max();
2339 vec3 nearest_obstacle_direction;
2340 bool found_forward_surface =
false;
2343 for (
uint primitive_id: valid_candidates) {
2348 if (vertices.empty()) {
2353 vec3 point_on_plane = vertices[0];
2356 vec3 to_origin = origin - point_on_plane;
2357 float distance_to_plane = to_origin * surface_normal;
2360 float surface_distance = std::abs(distance_to_plane);
2363 vec3 surface_direction;
2364 if (distance_to_plane > 0) {
2366 surface_direction = -surface_normal;
2369 surface_direction = surface_normal;
2373 float dot_product = surface_direction * direction;
2375 if (dot_product > 0.0f) {
2376 if (surface_distance < nearest_distance_found) {
2377 nearest_distance_found = surface_distance;
2378 nearest_obstacle_direction = surface_direction;
2379 found_forward_surface =
true;
2384 if (found_forward_surface) {
2385 distance = nearest_distance_found;
2386 obstacle_direction = nearest_obstacle_direction;
2397 std::vector<uint> effective_candidates;
2398 if (tree_based_bvh_enabled) {
2400 float spatial_filter_distance = height * 1.25f;
2404 effective_candidates = candidate_UUIDs;
2407 if (effective_candidates.empty()) {
2412 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
2413 if (printmessages) {
2414 std::cerr <<
"WARNING (CollisionDetection::findNearestSolidObstacleInCone): Invalid half_angle " << half_angle << std::endl;
2419 if (height <= 0.0f) {
2420 if (printmessages) {
2421 std::cerr <<
"WARNING (CollisionDetection::findNearestSolidObstacleInCone): Invalid height " << height << std::endl;
2430 if (bvh_nodes.empty()) {
2435 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
2438 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
2440 float nearest_distance = std::numeric_limits<float>::max();
2441 vec3 nearest_direction;
2442 bool found_obstacle =
false;
2445 std::vector<RayQuery> ray_queries;
2446 ray_queries.reserve(ray_directions.size());
2448 for (
const vec3 &ray_dir: ray_directions) {
2449 ray_queries.emplace_back(apex, ray_dir, height, candidate_UUIDs);
2454 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2457 for (
size_t i = 0; i < hit_results.size(); ++i) {
2458 const HitResult &result = hit_results[i];
2460 if (result.
hit && result.
distance < nearest_distance) {
2461 nearest_distance = result.
distance;
2462 nearest_direction = ray_directions[i];
2463 found_obstacle =
true;
2467 if (found_obstacle) {
2468 distance = nearest_distance;
2469 obstacle_direction = nearest_direction;
2477 vec3 &obstacle_direction,
int num_rays) {
2480 std::vector<uint> effective_candidates;
2481 if (tree_based_bvh_enabled) {
2485 if (printmessages && !effective_candidates.empty()) {
2486 std::cout <<
"Per-tree findNearestSolidObstacleInCone: Using " << effective_candidates.size() <<
" relevant targets instead of " << candidate_UUIDs.size() <<
" total targets" << std::endl;
2489 effective_candidates = candidate_UUIDs;
2492 if (effective_candidates.empty()) {
2497 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
2498 if (printmessages) {
2499 std::cerr <<
"WARNING (CollisionDetection::findNearestSolidObstacleInCone): Invalid half_angle " << half_angle << std::endl;
2504 if (height <= 0.0f) {
2505 if (printmessages) {
2506 std::cerr <<
"WARNING (CollisionDetection::findNearestSolidObstacleInCone): Invalid height " << height << std::endl;
2515 if (bvh_nodes.empty()) {
2520 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
2523 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
2525 float nearest_distance = std::numeric_limits<float>::max();
2526 vec3 nearest_direction;
2527 bool found_obstacle =
false;
2530 std::vector<RayQuery> ray_queries;
2531 ray_queries.reserve(ray_directions.size());
2533 for (
const vec3 &ray_dir: ray_directions) {
2534 ray_queries.emplace_back(apex, ray_dir, height, effective_candidates);
2539 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2542 for (
size_t i = 0; i < hit_results.size(); ++i) {
2543 const HitResult &result = hit_results[i];
2545 if (result.
hit && result.
distance < nearest_distance) {
2546 nearest_distance = result.
distance;
2547 nearest_direction = ray_directions[i];
2548 found_obstacle =
true;
2552 if (found_obstacle) {
2553 distance = nearest_distance;
2554 obstacle_direction = nearest_direction;
2562std::vector<helios::vec3> CollisionDetection::sampleDirectionsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
int num_samples) {
2564 std::vector<vec3> directions;
2565 directions.reserve(num_samples);
2567 if (num_samples <= 0 || half_angle <= 0.0f) {
2572 vec3 axis = central_axis;
2577 if (std::abs(axis.
z) < 0.9f) {
2587 std::random_device rd;
2588 std::mt19937 gen(rd());
2589 std::uniform_real_distribution<float> uniform_dist(0.0f, 1.0f);
2591 int samples_generated = 0;
2592 int max_attempts = num_samples * 10;
2595 while (samples_generated < num_samples && attempts < max_attempts) {
2599 float u1 = uniform_dist(gen);
2600 float u2 = uniform_dist(gen);
2603 if (samples_generated > 0) {
2604 float stratum_u1 = (float) samples_generated / (
float) num_samples;
2605 float stratum_u2 = uniform_dist(gen);
2606 u1 = (stratum_u1 + u1 / (float) num_samples);
2614 float cos_half_angle = cosf(half_angle);
2615 float cos_theta = cos_half_angle + u1 * (1.0f - cos_half_angle);
2616 float sin_theta = sqrtf(1.0f - cos_theta * cos_theta);
2617 float phi = 2.0f *
M_PI * u2;
2620 float x = sin_theta * cosf(phi);
2621 float y = sin_theta * sinf(phi);
2622 float z = cos_theta;
2626 vec3 world_direction = u * local_direction.
x + v * local_direction.
y + axis * local_direction.
z;
2630 float dot_product = world_direction * axis;
2631 if (dot_product >= cos_half_angle - 1e-6f) {
2632 directions.push_back(world_direction);
2633 samples_generated++;
2638 while (directions.size() < (
size_t) num_samples) {
2639 directions.push_back(axis);
2648std::vector<uint> CollisionDetection::getCandidatesUsingSpatialGrid(
const Cone &cone,
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
2650 vec3 cone_base = apex + central_axis * height;
2651 float cone_base_radius = height * tan(half_angle);
2653 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));
2654 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));
2657 std::vector<uint> all_primitives;
2658 if (!primitive_indices.empty()) {
2659 all_primitives = primitive_indices;
2666 return filterPrimitivesParallel(cone, all_primitives);
2671std::vector<uint> CollisionDetection::getCandidatePrimitivesInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
2672 std::vector<uint> candidates;
2675 Cone cone{apex, central_axis, half_angle, height};
2678 candidates = getCandidatesUsingSpatialGrid(cone, apex, central_axis, half_angle, height);
2686std::vector<CollisionDetection::Gap> CollisionDetection::detectGapsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height,
int num_samples) {
2688 std::vector<Gap> gaps;
2691 std::vector<vec3> sample_directions = sampleDirectionsInCone(apex, central_axis, half_angle, num_samples);
2693 if (sample_directions.empty()) {
2698 std::vector<RaySample> ray_samples;
2699 ray_samples.reserve(sample_directions.size());
2701 float max_distance = (height > 0.0f) ? height : -1.0f;
2704 std::vector<RayQuery> ray_queries;
2705 ray_queries.reserve(sample_directions.size());
2707 for (
const vec3 &direction: sample_directions) {
2708 ray_queries.emplace_back(apex, direction, max_distance);
2712 RayTracingStats ray_stats;
2713 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2716 for (
size_t i = 0; i < hit_results.size(); ++i) {
2718 sample.direction = sample_directions[i];
2720 if (hit_results[i].hit) {
2721 sample.distance = hit_results[i].distance;
2722 sample.is_free =
false;
2724 sample.distance = (max_distance > 0.0f) ? max_distance : 1000.0f;
2725 sample.is_free =
true;
2728 ray_samples.push_back(sample);
2734 std::vector<std::pair<float, size_t>> angular_positions;
2735 for (
size_t i = 0; i < ray_samples.size(); ++i) {
2736 if (ray_samples[i].is_free) {
2738 float dot_product = ray_samples[i].direction * central_axis;
2739 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2740 float angular_from_center = acosf(dot_product);
2741 angular_positions.push_back({angular_from_center, i});
2745 if (angular_positions.empty()) {
2750 std::sort(angular_positions.begin(), angular_positions.end());
2753 std::vector<bool> processed(ray_samples.size(),
false);
2754 float min_gap_angular_size = half_angle * 0.05f;
2756 for (
size_t start = 0; start < angular_positions.size(); ++start) {
2757 size_t start_idx = angular_positions[start].second;
2758 if (processed[start_idx])
2762 new_gap.sample_indices.push_back(start_idx);
2763 processed[start_idx] =
true;
2766 std::vector<float> distances_to_start;
2767 for (
size_t j = 0; j < ray_samples.size(); ++j) {
2768 if (j != start_idx && ray_samples[j].is_free && !processed[j]) {
2769 float dot_product = ray_samples[start_idx].direction * ray_samples[j].direction;
2770 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2771 float angular_distance = acosf(dot_product);
2772 distances_to_start.push_back(angular_distance);
2774 distances_to_start.push_back(999.0f);
2779 float sample_density = 2.0f * half_angle / sqrtf((
float) num_samples);
2780 float adaptive_threshold = sample_density * 3.0f;
2782 for (
size_t j = 0; j < ray_samples.size(); ++j) {
2783 if (j != start_idx && ray_samples[j].is_free && !processed[j] && distances_to_start[j] < adaptive_threshold) {
2784 new_gap.sample_indices.push_back(j);
2785 processed[j] =
true;
2790 if (new_gap.sample_indices.size() >= 5) {
2791 gaps.push_back(new_gap);
2796 for (Gap &gap: gaps) {
2798 vec3 center(0, 0, 0);
2799 for (
int idx: gap.sample_indices) {
2800 center = center + ray_samples[idx].direction;
2802 center = center / (float) gap.sample_indices.size();
2804 gap.center_direction = center;
2807 std::vector<RaySample> gap_samples;
2808 for (
int idx: gap.sample_indices) {
2809 gap_samples.push_back(ray_samples[idx]);
2811 gap.angular_size = calculateGapAngularSize(gap_samples, central_axis);
2814 float dot_product = gap.center_direction * central_axis;
2815 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2816 gap.angular_distance = acosf(dot_product);
2821 if (gaps.size() > 10) {
2822 float max_angular_distance = half_angle * 0.8f;
2824 auto it = std::remove_if(gaps.begin(), gaps.end(), [max_angular_distance](
const Gap &gap) { return gap.angular_distance > max_angular_distance; });
2826 gaps.erase(it, gaps.end());
2829 if (gaps.size() < 3 && gaps.size() > 0) {
2831 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; });
2839float CollisionDetection::calculateGapAngularSize(
const std::vector<RaySample> &gap_samples,
const vec3 ¢ral_axis) {
2841 if (gap_samples.empty()) {
2846 float min_angle =
M_PI;
2847 float max_angle = 0.0f;
2849 for (
const RaySample &sample: gap_samples) {
2850 float dot_product = sample.direction * central_axis;
2851 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
2852 float angle = acosf(dot_product);
2854 min_angle = std::min(min_angle, angle);
2855 max_angle = std::max(max_angle, angle);
2859 float angular_width = max_angle - min_angle;
2863 float solid_angle =
M_PI * angular_width * angular_width;
2868void CollisionDetection::scoreGapsByFishEyeMetric(std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
2871 if (gaps.size() <= 10) {
2872 for (Gap &gap: gaps) {
2875 float size_score = log(1.0f + gap.angular_size * 100.0f);
2877 float distance_penalty = exp(gap.angular_distance * 2.0f);
2879 gap.score = size_score / distance_penalty;
2882 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
2888 const size_t max_gaps_needed = std::min(
size_t(5), gaps.size());
2891 for (Gap &gap: gaps) {
2895 float size_score = log(1.0f + gap.angular_size * 100.0f);
2898 float distance_penalty = exp(gap.angular_distance * 2.0f);
2901 gap.score = size_score / distance_penalty;
2905 std::partial_sort(gaps.begin(), gaps.begin() + max_gaps_needed, gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
2908 gaps.resize(max_gaps_needed);
2911helios::vec3 CollisionDetection::findOptimalGapDirection(
const std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
2915 vec3 result = central_axis;
2921 const Gap &best_gap = gaps[0];
2922 return best_gap.center_direction;
2929 std::vector<std::pair<uint, uint>> collision_pairs;
2933 for (
uint target_id: target_UUIDs) {
2934 if (primitive_centroids_cache.find(target_id) == primitive_centroids_cache.end()) {
2937 if (!vertices.empty()) {
2939 for (
const vec3 &vertex: vertices) {
2940 centroid = centroid + vertex;
2942 centroid = centroid / float(vertices.size());
2943 primitive_centroids_cache[target_id] = centroid;
2949 for (
uint query_id: query_UUIDs) {
2952 if (query_vertices.empty())
2956 for (
const vec3 &vertex: query_vertices) {
2957 query_centroid = query_centroid + vertex;
2959 query_centroid = query_centroid / float(query_vertices.size());
2962 for (
uint target_id: target_UUIDs) {
2963 if (query_id == target_id)
2966 auto target_centroid_it = primitive_centroids_cache.find(target_id);
2967 if (target_centroid_it != primitive_centroids_cache.end()) {
2968 vec3 target_centroid = target_centroid_it->second;
2969 float distance = (query_centroid - target_centroid).magnitude();
2971 if (distance <= max_distance) {
2973 std::vector<uint> single_query = {query_id};
2974 std::vector<uint> single_target = {target_id};
2975 std::vector<uint> empty_objects;
2977 std::vector<uint> collisions =
findCollisions(single_query, empty_objects, single_target, empty_objects);
2978 if (!collisions.empty()) {
2979 collision_pairs.push_back(std::make_pair(query_id, target_id));
2986 return collision_pairs;
2990 if (distance <= 0.0f) {
2991 helios_runtime_error(
"ERROR (CollisionDetection::setMaxCollisionDistance): Distance must be positive");
2994 max_collision_distance = distance;
2998 return max_collision_distance;
3003 std::vector<uint> filtered_UUIDs;
3006 std::vector<uint> candidates;
3007 if (candidate_UUIDs.empty()) {
3010 candidates = candidate_UUIDs;
3014 for (
uint candidate_id: candidates) {
3022 auto cache_it = primitive_centroids_cache.find(candidate_id);
3023 if (cache_it != primitive_centroids_cache.end()) {
3024 centroid = cache_it->second;
3028 if (vertices.empty())
3032 for (
const vec3 &vertex: vertices) {
3033 centroid = centroid + vertex;
3035 centroid = centroid / float(vertices.size());
3036 primitive_centroids_cache[candidate_id] = centroid;
3040 float distance = (query_center - centroid).magnitude();
3041 if (distance <= max_radius) {
3042 filtered_UUIDs.push_back(candidate_id);
3046 return filtered_UUIDs;
3053 if (ray_origins.size() != ray_directions.size()) {
3054 helios_runtime_error(
"ERROR (CollisionDetection::calculateVoxelRayPathLengths): ray_origins and ray_directions vectors must have same size");
3057 if (ray_origins.empty()) {
3058 if (printmessages) {
3059 std::cerr <<
"WARNING (CollisionDetection::calculateVoxelRayPathLengths): No rays provided" << std::endl;
3065 initializeVoxelData(grid_center, grid_size, grid_divisions);
3070 if (primitive_cache.empty()) {
3071 buildPrimitiveCache();
3075#ifdef HELIOS_CUDA_AVAILABLE
3077 calculateVoxelRayPathLengths_GPU(ray_origins, ray_directions);
3079 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3082 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3087 if (!validateVoxelIndices(ijk)) {
3088 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Invalid voxel indices");
3091 if (!voxel_data_initialized) {
3092 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3095 if (use_flat_arrays) {
3096 size_t flat_idx = flatIndex(ijk);
3097 voxel_ray_counts_flat[flat_idx] = P_denom;
3098 voxel_transmitted_flat[flat_idx] = P_trans;
3100 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = P_denom;
3101 voxel_transmitted[ijk.
x][ijk.
y][ijk.
z] = P_trans;
3106 if (!validateVoxelIndices(ijk)) {
3107 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelTransmissionProbability): Invalid voxel indices");
3110 if (!voxel_data_initialized) {
3116 if (use_flat_arrays) {
3117 size_t flat_idx = flatIndex(ijk);
3118 P_denom = voxel_ray_counts_flat[flat_idx];
3119 P_trans = voxel_transmitted_flat[flat_idx];
3121 P_denom = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3122 P_trans = voxel_transmitted[ijk.
x][ijk.
y][ijk.
z];
3127 if (!validateVoxelIndices(ijk)) {
3131 if (!voxel_data_initialized) {
3132 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelRbar): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3135 if (use_flat_arrays) {
3136 size_t flat_idx = flatIndex(ijk);
3138 int ray_count = voxel_ray_counts_flat[flat_idx];
3139 if (ray_count == 0) {
3141 voxel_ray_counts_flat[flat_idx] = 1;
3143 voxel_path_lengths_flat[flat_idx] = r_bar *
static_cast<float>(ray_count);
3146 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3147 if (ray_count == 0) {
3149 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = 1;
3151 voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] = r_bar *
static_cast<float>(ray_count);
3156 if (!validateVoxelIndices(ijk)) {
3160 if (!voxel_data_initialized) {
3164 if (use_flat_arrays) {
3165 size_t flat_idx = flatIndex(ijk);
3166 int ray_count = voxel_ray_counts_flat[flat_idx];
3167 if (ray_count == 0) {
3172 return voxel_path_lengths_flat[flat_idx] /
static_cast<float>(ray_count);
3174 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3175 if (ray_count == 0) {
3180 return voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] /
static_cast<float>(ray_count);
3185 if (!validateVoxelIndices(ijk)) {
3186 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayHitCounts): Invalid voxel indices");
3189 if (!voxel_data_initialized) {
3196 if (use_flat_arrays) {
3197 size_t flat_idx = flatIndex(ijk);
3198 hit_before = voxel_hit_before_flat[flat_idx];
3199 hit_after = voxel_hit_after_flat[flat_idx];
3200 hit_inside = voxel_hit_inside_flat[flat_idx];
3202 hit_before = voxel_hit_before[ijk.
x][ijk.
y][ijk.
z];
3203 hit_after = voxel_hit_after[ijk.
x][ijk.
y][ijk.
z];
3204 hit_inside = voxel_hit_inside[ijk.
x][ijk.
y][ijk.
z];
3209 if (!validateVoxelIndices(ijk)) {
3210 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayPathLengths): Invalid voxel indices");
3213 if (!voxel_data_initialized) {
3214 return std::vector<float>();
3217 if (use_flat_arrays) {
3219 size_t flat_idx = flatIndex(ijk);
3222 if (flat_idx >= voxel_individual_path_offsets.size() || flat_idx >= voxel_individual_path_counts.size()) {
3223 return std::vector<float>();
3226 size_t offset = voxel_individual_path_offsets[flat_idx];
3227 size_t count = voxel_individual_path_counts[flat_idx];
3230 if (count == 0 || offset + count > voxel_individual_path_lengths_flat.size()) {
3231 return std::vector<float>();
3234 std::vector<float> result;
3235 result.reserve(count);
3237 for (
size_t i = 0; i < count; ++i) {
3238 result.push_back(voxel_individual_path_lengths_flat[offset + i]);
3243 return voxel_individual_path_lengths[ijk.
x][ijk.
y][ijk.
z];
3248 voxel_ray_counts.clear();
3249 voxel_transmitted.clear();
3250 voxel_path_lengths.clear();
3251 voxel_hit_before.clear();
3252 voxel_hit_after.clear();
3253 voxel_hit_inside.clear();
3254 voxel_individual_path_lengths.clear();
3255 voxel_data_initialized =
false;
3257 if (printmessages) {
3258 std::cout <<
"Voxel data cleared." << std::endl;
3264void CollisionDetection::initializeVoxelData(
const vec3 &grid_center,
const vec3 &grid_size,
const helios::int3 &grid_divisions) {
3267 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 ||
3268 grid_divisions.
z != voxel_grid_divisions.
z;
3272 if (use_flat_arrays) {
3274 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3275 std::fill(voxel_ray_counts_flat.begin(), voxel_ray_counts_flat.end(), 0);
3276 std::fill(voxel_transmitted_flat.begin(), voxel_transmitted_flat.end(), 0);
3277 std::fill(voxel_path_lengths_flat.begin(), voxel_path_lengths_flat.end(), 0.0f);
3278 std::fill(voxel_hit_before_flat.begin(), voxel_hit_before_flat.end(), 0);
3279 std::fill(voxel_hit_after_flat.begin(), voxel_hit_after_flat.end(), 0);
3280 std::fill(voxel_hit_inside_flat.begin(), voxel_hit_inside_flat.end(), 0);
3283 voxel_individual_path_lengths_flat.clear();
3284 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
3285 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
3288 for (
int i = 0; i < grid_divisions.
x; i++) {
3289 for (
int j = 0; j < grid_divisions.
y; j++) {
3290 for (
int k = 0; k < grid_divisions.
z; k++) {
3291 voxel_ray_counts[i][j][k] = 0;
3292 voxel_transmitted[i][j][k] = 0;
3293 voxel_path_lengths[i][j][k] = 0.0f;
3294 voxel_hit_before[i][j][k] = 0;
3295 voxel_hit_after[i][j][k] = 0;
3296 voxel_hit_inside[i][j][k] = 0;
3297 voxel_individual_path_lengths[i][j][k].clear();
3306 voxel_grid_center = grid_center;
3307 voxel_grid_size = grid_size;
3308 voxel_grid_divisions = grid_divisions;
3311 use_flat_arrays =
true;
3313 if (use_flat_arrays) {
3315 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3317 voxel_ray_counts_flat.assign(total_voxels, 0);
3318 voxel_transmitted_flat.assign(total_voxels, 0);
3319 voxel_path_lengths_flat.assign(total_voxels, 0.0f);
3320 voxel_hit_before_flat.assign(total_voxels, 0);
3321 voxel_hit_after_flat.assign(total_voxels, 0);
3322 voxel_hit_inside_flat.assign(total_voxels, 0);
3325 voxel_individual_path_lengths_flat.clear();
3326 voxel_individual_path_offsets.assign(total_voxels, 0);
3327 voxel_individual_path_counts.assign(total_voxels, 0);
3330 voxel_individual_path_lengths_flat.reserve(total_voxels * 10);
3334 voxel_ray_counts.resize(grid_divisions.
x);
3335 voxel_transmitted.resize(grid_divisions.
x);
3336 voxel_path_lengths.resize(grid_divisions.
x);
3337 voxel_hit_before.resize(grid_divisions.
x);
3338 voxel_hit_after.resize(grid_divisions.
x);
3339 voxel_hit_inside.resize(grid_divisions.
x);
3340 voxel_individual_path_lengths.resize(grid_divisions.
x);
3342 for (
int i = 0; i < grid_divisions.
x; i++) {
3343 voxel_ray_counts[i].resize(grid_divisions.
y);
3344 voxel_transmitted[i].resize(grid_divisions.
y);
3345 voxel_path_lengths[i].resize(grid_divisions.
y);
3346 voxel_hit_before[i].resize(grid_divisions.
y);
3347 voxel_hit_after[i].resize(grid_divisions.
y);
3348 voxel_hit_inside[i].resize(grid_divisions.
y);
3349 voxel_individual_path_lengths[i].resize(grid_divisions.
y);
3351 for (
int j = 0; j < grid_divisions.
y; j++) {
3352 voxel_ray_counts[i][j].resize(grid_divisions.
z, 0);
3353 voxel_transmitted[i][j].resize(grid_divisions.
z, 0);
3354 voxel_path_lengths[i][j].resize(grid_divisions.
z, 0.0f);
3355 voxel_hit_before[i][j].resize(grid_divisions.
z, 0);
3356 voxel_hit_after[i][j].resize(grid_divisions.
z, 0);
3357 voxel_hit_inside[i][j].resize(grid_divisions.
z, 0);
3358 voxel_individual_path_lengths[i][j].resize(grid_divisions.
z);
3363 voxel_data_initialized =
true;
3366bool CollisionDetection::validateVoxelIndices(
const helios::int3 &ijk)
const {
3367 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);
3370void CollisionDetection::calculateVoxelAABB(
const helios::int3 &ijk,
vec3 &voxel_min,
vec3 &voxel_max)
const {
3371 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));
3373 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3375 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);
3377 voxel_max = voxel_min + voxel_size;
3380std::vector<std::pair<helios::int3, float>> CollisionDetection::traverseVoxelGrid(
const vec3 &ray_origin,
const vec3 &ray_direction)
const {
3381 std::vector<std::pair<helios::int3, float>> traversed_voxels;
3384 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3385 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
3386 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));
3389 float t_grid_min, t_grid_max;
3390 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max)) {
3391 return traversed_voxels;
3395 if (t_grid_max <= 1e-6) {
3396 return traversed_voxels;
3400 t_grid_min = std::max(0.0f, t_grid_min);
3403 if (voxel_grid_divisions.
x == 1 && voxel_grid_divisions.
y == 1 && voxel_grid_divisions.
z == 1) {
3404 float path_length = t_grid_max - t_grid_min;
3405 if (path_length > 1e-6f) {
3406 traversed_voxels.emplace_back(helios::make_int3(0, 0, 0), path_length);
3408 return traversed_voxels;
3412 vec3 start_pos = ray_origin + t_grid_min * ray_direction;
3416 current_voxel.
x =
static_cast<int>(std::floor((start_pos.
x - grid_min.
x) / voxel_size.
x));
3417 current_voxel.
y =
static_cast<int>(std::floor((start_pos.
y - grid_min.
y) / voxel_size.
y));
3418 current_voxel.
z =
static_cast<int>(std::floor((start_pos.
z - grid_min.
z) / voxel_size.
z));
3421 current_voxel.
x = std::max(0, std::min(current_voxel.
x, voxel_grid_divisions.
x - 1));
3422 current_voxel.
y = std::max(0, std::min(current_voxel.
y, voxel_grid_divisions.
y - 1));
3423 current_voxel.
z = std::max(0, std::min(current_voxel.
z, voxel_grid_divisions.
z - 1));
3427 vec3 t_delta, t_max;
3430 for (
int i = 0; i < 3; i++) {
3431 float dir_comp = (i == 0) ? ray_direction.
x : (i == 1) ? ray_direction.y : ray_direction.z;
3432 float size_comp = (i == 0) ? voxel_size.
x : (i == 1) ? voxel_size.y : voxel_size.z;
3433 float grid_min_comp = (i == 0) ? grid_min.
x : (i == 1) ? grid_min.y : grid_min.z;
3434 float start_comp = (i == 0) ? start_pos.
x : (i == 1) ? start_pos.y : start_pos.z;
3435 int current_comp = (i == 0) ? current_voxel.
x : (i == 1) ? current_voxel.y : current_voxel.z;
3436 int max_comp = (i == 0) ? voxel_grid_divisions.
x : (i == 1) ? voxel_grid_divisions.y : voxel_grid_divisions.z;
3438 if (std::abs(dir_comp) < 1e-8f) {
3444 }
else if (i == 1) {
3456 step.
x = (dir_comp > 0) ? 1 : -1;
3457 t_delta.
x = std::abs(size_comp / dir_comp);
3460 t_max.
x = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3462 t_max.
x = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3464 }
else if (i == 1) {
3465 step.
y = (dir_comp > 0) ? 1 : -1;
3466 t_delta.
y = std::abs(size_comp / dir_comp);
3469 t_max.
y = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3471 t_max.
y = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3474 step.
z = (dir_comp > 0) ? 1 : -1;
3475 t_delta.
z = std::abs(size_comp / dir_comp);
3478 t_max.
z = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
3480 t_max.
z = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
3487 float current_t = t_grid_min;
3489 while (validateVoxelIndices(current_voxel) && current_t < t_grid_max) {
3491 float next_t = std::min({t_max.
x, t_max.
y, t_max.
z, t_grid_max});
3492 float path_length = next_t - current_t;
3494 if (path_length > 1e-6f) {
3495 traversed_voxels.emplace_back(current_voxel, path_length);
3499 if (next_t >= t_grid_max) {
3504 if (t_max.
x <= t_max.
y && t_max.
x <= t_max.
z) {
3505 current_voxel.
x += step.
x;
3506 t_max.
x += t_delta.
x;
3507 }
else if (t_max.
y <= t_max.
z) {
3508 current_voxel.
y += step.
y;
3509 t_max.
y += t_delta.
y;
3511 current_voxel.
z += step.
z;
3512 t_max.
z += t_delta.
z;
3518 return traversed_voxels;
3521void CollisionDetection::calculateVoxelRayPathLengths_CPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
3524 auto start_time = std::chrono::high_resolution_clock::now();
3527 std::atomic<long long> total_raycast_time(0);
3528 std::atomic<int> raycast_count(0);
3533 const int num_rays =
static_cast<int>(ray_origins.size());
3536 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
3539 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3540 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
3543 const int num_threads = omp_get_max_threads();
3546 std::vector<std::vector<int>> thread_ray_counts(num_threads, std::vector<int>(total_voxels, 0));
3547 std::vector<std::vector<float>> thread_path_lengths(num_threads, std::vector<float>(total_voxels, 0.0f));
3548 std::vector<std::vector<int>> thread_hit_before(num_threads, std::vector<int>(total_voxels, 0));
3549 std::vector<std::vector<int>> thread_hit_after(num_threads, std::vector<int>(total_voxels, 0));
3550 std::vector<std::vector<int>> thread_hit_inside(num_threads, std::vector<int>(total_voxels, 0));
3551 std::vector<std::vector<int>> thread_transmitted(num_threads, std::vector<int>(total_voxels, 0));
3552 std::vector<std::vector<std::vector<float>>> thread_individual_paths(num_threads, std::vector<std::vector<float>>(total_voxels));
3555#pragma omp parallel for schedule(dynamic)
3556 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
3557 const int thread_id = omp_get_thread_num();
3558 const vec3 &ray_origin = ray_origins[ray_idx];
3559 const vec3 &ray_direction = ray_directions[ray_idx];
3562 float t_grid_min, t_grid_max;
3563 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
3568 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
3571 if (traversed_voxels.empty()) {
3576 auto raycast_start = std::chrono::high_resolution_clock::now();
3577 RayQuery query(ray_origin, ray_direction, -1.0f, {});
3578 HitResult hit =
castRay(query);
3579 auto raycast_end = std::chrono::high_resolution_clock::now();
3582 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
3585 float hit_distance = hit.hit ? hit.distance : 1e30f;
3588 for (
const auto &voxel_data: traversed_voxels) {
3590 float path_length = voxel_data.second;
3593 vec3 voxel_min, voxel_max;
3594 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
3598 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
3605 bool hit_before =
false;
3606 bool hit_after =
false;
3607 bool hit_inside =
false;
3611 if (hit_distance < t_min) {
3614 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
3628 size_t flat_idx = flatIndex(voxel_idx);
3631 thread_ray_counts[thread_id][flat_idx]++;
3632 thread_path_lengths[thread_id][flat_idx] += path_length;
3635 thread_hit_before[thread_id][flat_idx]++;
3638 thread_hit_after[thread_id][flat_idx]++;
3641 thread_hit_inside[thread_id][flat_idx]++;
3643 thread_transmitted[thread_id][flat_idx]++;
3647 thread_individual_paths[thread_id][flat_idx].push_back(path_length);
3653 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3654 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3655 voxel_ray_counts_flat[voxel_idx] += thread_ray_counts[thread_id][voxel_idx];
3656 voxel_path_lengths_flat[voxel_idx] += thread_path_lengths[thread_id][voxel_idx];
3657 voxel_hit_before_flat[voxel_idx] += thread_hit_before[thread_id][voxel_idx];
3658 voxel_hit_after_flat[voxel_idx] += thread_hit_after[thread_id][voxel_idx];
3659 voxel_hit_inside_flat[voxel_idx] += thread_hit_inside[thread_id][voxel_idx];
3660 voxel_transmitted_flat[voxel_idx] += thread_transmitted[thread_id][voxel_idx];
3665 if (use_flat_arrays) {
3667 voxel_individual_path_lengths_flat.clear();
3670 size_t total_paths = 0;
3671 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3672 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3673 total_paths += thread_individual_paths[thread_id][voxel_idx].size();
3676 voxel_individual_path_lengths_flat.reserve(total_paths);
3679 size_t current_offset = 0;
3680 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3681 voxel_individual_path_offsets[voxel_idx] = current_offset;
3682 size_t voxel_path_count = 0;
3685 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
3686 for (
float path_length: thread_individual_paths[thread_id][voxel_idx]) {
3687 voxel_individual_path_lengths_flat.push_back(path_length);
3692 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
3693 current_offset += voxel_path_count;
3698 const int num_threads = 1;
3699 const int thread_id = 0;
3702 std::vector<int> serial_ray_counts(total_voxels, 0);
3703 std::vector<float> serial_path_lengths(total_voxels, 0.0f);
3704 std::vector<int> serial_hit_before(total_voxels, 0);
3705 std::vector<int> serial_hit_after(total_voxels, 0);
3706 std::vector<int> serial_hit_inside(total_voxels, 0);
3707 std::vector<int> serial_transmitted(total_voxels, 0);
3708 std::vector<std::vector<float>> serial_individual_paths(total_voxels);
3711 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
3712 const vec3 &ray_origin = ray_origins[ray_idx];
3713 const vec3 &ray_direction = ray_directions[ray_idx];
3716 float t_grid_min, t_grid_max;
3717 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
3722 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
3725 if (traversed_voxels.empty()) {
3730 auto raycast_start = std::chrono::high_resolution_clock::now();
3731 RayQuery query(ray_origin, ray_direction, -1.0f, {});
3732 HitResult hit =
castRay(query);
3733 auto raycast_end = std::chrono::high_resolution_clock::now();
3736 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
3739 float hit_distance = hit.hit ? hit.distance : 1e30f;
3742 for (
const auto &voxel_data: traversed_voxels) {
3744 float path_length = voxel_data.second;
3747 vec3 voxel_min, voxel_max;
3748 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
3752 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
3759 bool hit_before =
false;
3760 bool hit_after =
false;
3761 bool hit_inside =
false;
3765 if (hit_distance < t_min) {
3768 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
3782 size_t flat_idx = flatIndex(voxel_idx);
3784 serial_ray_counts[flat_idx]++;
3785 serial_path_lengths[flat_idx] += path_length;
3788 serial_hit_before[flat_idx]++;
3791 serial_hit_after[flat_idx]++;
3794 serial_hit_inside[flat_idx]++;
3796 serial_transmitted[flat_idx]++;
3800 serial_individual_paths[flat_idx].push_back(path_length);
3805 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3806 voxel_ray_counts_flat[voxel_idx] += serial_ray_counts[voxel_idx];
3807 voxel_path_lengths_flat[voxel_idx] += serial_path_lengths[voxel_idx];
3808 voxel_hit_before_flat[voxel_idx] += serial_hit_before[voxel_idx];
3809 voxel_hit_after_flat[voxel_idx] += serial_hit_after[voxel_idx];
3810 voxel_hit_inside_flat[voxel_idx] += serial_hit_inside[voxel_idx];
3811 voxel_transmitted_flat[voxel_idx] += serial_transmitted[voxel_idx];
3815 if (use_flat_arrays) {
3817 voxel_individual_path_lengths_flat.clear();
3820 size_t total_paths = 0;
3821 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3822 total_paths += serial_individual_paths[voxel_idx].size();
3824 voxel_individual_path_lengths_flat.reserve(total_paths);
3827 size_t current_offset = 0;
3828 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3829 voxel_individual_path_offsets[voxel_idx] = current_offset;
3830 size_t voxel_path_count = serial_individual_paths[voxel_idx].size();
3833 for (
float path_length: serial_individual_paths[voxel_idx]) {
3834 voxel_individual_path_lengths_flat.push_back(path_length);
3837 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
3838 current_offset += voxel_path_count;
3843 auto end_time = std::chrono::high_resolution_clock::now();
3844 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
3847 long long avg_raycast_time = raycast_count > 0 ? total_raycast_time.load() / raycast_count.load() : 0;
3870#ifdef HELIOS_CUDA_AVAILABLE
3871void CollisionDetection::calculateVoxelRayPathLengths_GPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
3872 if (printmessages) {
3875 auto start_time = std::chrono::high_resolution_clock::now();
3877 const int num_rays =
static_cast<int>(ray_origins.size());
3878 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
3881 std::vector<float> h_ray_origins(num_rays * 3);
3882 std::vector<float> h_ray_directions(num_rays * 3);
3883 std::vector<int> h_voxel_ray_counts(total_voxels, 0);
3884 std::vector<float> h_voxel_path_lengths(total_voxels, 0.0f);
3885 std::vector<int> h_voxel_transmitted(total_voxels, 0);
3886 std::vector<int> h_voxel_hit_before(total_voxels, 0);
3887 std::vector<int> h_voxel_hit_after(total_voxels, 0);
3888 std::vector<int> h_voxel_hit_inside(total_voxels, 0);
3891 for (
int i = 0; i < num_rays; i++) {
3892 h_ray_origins[i * 3 + 0] = ray_origins[i].x;
3893 h_ray_origins[i * 3 + 1] = ray_origins[i].y;
3894 h_ray_origins[i * 3 + 2] = ray_origins[i].z;
3896 h_ray_directions[i * 3 + 0] = ray_directions[i].x;
3897 h_ray_directions[i * 3 + 1] = ray_directions[i].y;
3898 h_ray_directions[i * 3 + 2] = ray_directions[i].z;
3902 int primitive_count =
static_cast<int>(primitive_cache.size());
3905 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,
3906 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(),
3907 h_voxel_hit_inside.data());
3910 if (use_flat_arrays) {
3912 voxel_ray_counts_flat = h_voxel_ray_counts;
3913 voxel_path_lengths_flat = h_voxel_path_lengths;
3914 voxel_transmitted_flat = h_voxel_transmitted;
3917 voxel_hit_before_flat = h_voxel_hit_before;
3918 voxel_hit_after_flat = h_voxel_hit_after;
3919 voxel_hit_inside_flat = h_voxel_hit_inside;
3924 voxel_individual_path_lengths_flat.clear();
3925 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
3926 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
3931 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
3932 voxel_individual_path_offsets[voxel_idx] = voxel_individual_path_lengths_flat.size();
3934 if (h_voxel_ray_counts[voxel_idx] > 0) {
3937 std::vector<float> estimated_paths;
3940 int voxel_z = voxel_idx % voxel_grid_divisions.
z;
3941 int voxel_y = (voxel_idx / voxel_grid_divisions.
z) % voxel_grid_divisions.
y;
3942 int voxel_x = voxel_idx / (voxel_grid_divisions.
y * voxel_grid_divisions.
z);
3945 vec3 voxel_size = voxel_grid_size;
3946 voxel_size.
x /= voxel_grid_divisions.
x;
3947 voxel_size.
y /= voxel_grid_divisions.
y;
3948 voxel_size.
z /= voxel_grid_divisions.
z;
3950 vec3 voxel_min = voxel_grid_center - voxel_grid_size * 0.5f;
3951 voxel_min.
x += voxel_x * voxel_size.
x;
3952 voxel_min.
y += voxel_y * voxel_size.
y;
3953 voxel_min.
z += voxel_z * voxel_size.
z;
3955 vec3 voxel_max = voxel_min + voxel_size;
3958 for (
int ray_idx = 0; ray_idx < num_rays; ++ray_idx) {
3959 vec3 ray_origin = ray_origins[ray_idx];
3960 vec3 ray_dir = ray_directions[ray_idx];
3964 float t_max = std::numeric_limits<float>::max();
3967 for (
int axis = 0; axis < 3; ++axis) {
3968 float origin_comp = (axis == 0) ? ray_origin.
x : (axis == 1) ? ray_origin.y : ray_origin.z;
3969 float dir_comp = (axis == 0) ? ray_dir.
x : (axis == 1) ? ray_dir.y : ray_dir.z;
3970 float min_comp = (axis == 0) ? voxel_min.
x : (axis == 1) ? voxel_min.y : voxel_min.z;
3971 float max_comp = (axis == 0) ? voxel_max.
x : (axis == 1) ? voxel_max.y : voxel_max.z;
3973 if (std::abs(dir_comp) < 1e-9f) {
3975 if (origin_comp < min_comp || origin_comp > max_comp) {
3980 float t1 = (min_comp - origin_comp) / dir_comp;
3981 float t2 = (max_comp - origin_comp) / dir_comp;
3986 t_min = std::max(t_min, t1);
3987 t_max = std::min(t_max, t2);
3995 if (t_max > t_min && t_max > 0.0f) {
3996 float entry_t = std::max(0.0f, t_min);
3997 float exit_t = t_max;
3998 float path_length = exit_t - entry_t;
4000 if (path_length > 1e-6f) {
4001 estimated_paths.push_back(path_length);
4007 for (
float path_length: estimated_paths) {
4008 voxel_individual_path_lengths_flat.push_back(path_length);
4010 voxel_individual_path_counts[voxel_idx] = estimated_paths.size();
4012 voxel_individual_path_counts[voxel_idx] = 0;
4018 for (
int i = 0; i < voxel_grid_divisions.
x; i++) {
4019 for (
int j = 0; j < voxel_grid_divisions.
y; j++) {
4020 for (
int k = 0; k < voxel_grid_divisions.
z; k++) {
4021 int flat_idx = i * voxel_grid_divisions.
y * voxel_grid_divisions.
z + j * voxel_grid_divisions.
z + k;
4022 voxel_ray_counts[i][j][k] = h_voxel_ray_counts[flat_idx];
4023 voxel_path_lengths[i][j][k] = h_voxel_path_lengths[flat_idx];
4024 voxel_transmitted[i][j][k] = h_voxel_transmitted[flat_idx];
4027 voxel_hit_before[i][j][k] = h_voxel_hit_before[flat_idx];
4028 voxel_hit_after[i][j][k] = h_voxel_hit_after[flat_idx];
4029 voxel_hit_inside[i][j][k] = h_voxel_hit_inside[flat_idx];
4032 voxel_individual_path_lengths[i][j][k].clear();
4033 if (h_voxel_ray_counts[flat_idx] > 0) {
4034 float avg_path_length = h_voxel_path_lengths[flat_idx] / h_voxel_ray_counts[flat_idx];
4035 for (
int ray = 0; ray < h_voxel_ray_counts[flat_idx]; ++ray) {
4036 voxel_individual_path_lengths[i][j][k].push_back(avg_path_length);
4044 auto end_time = std::chrono::high_resolution_clock::now();
4045 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4047 if (printmessages) {
4050 int total_ray_voxel_intersections = 0;
4051 for (
const auto &count: h_voxel_ray_counts) {
4052 total_ray_voxel_intersections += count;
4058void CollisionDetection::ensureOptimizedBVH() {
4061 if (soa_dirty || bvh_nodes_soa.node_count != bvh_nodes.size() || bvh_nodes_soa.aabb_mins.empty()) {
4063 if (bvh_nodes.empty()) {
4064 bvh_nodes_soa.clear();
4065 bvh_nodes_soa.node_count = 0;
4069 size_t node_count = bvh_nodes.size();
4072 bvh_nodes_soa.node_count = node_count;
4073 bvh_nodes_soa.aabb_mins.resize(node_count);
4074 bvh_nodes_soa.aabb_maxs.resize(node_count);
4075 bvh_nodes_soa.left_children.resize(node_count);
4076 bvh_nodes_soa.right_children.resize(node_count);
4077 bvh_nodes_soa.primitive_starts.resize(node_count);
4078 bvh_nodes_soa.primitive_counts.resize(node_count);
4079 bvh_nodes_soa.is_leaf_flags.resize(node_count);
4082 for (
size_t i = 0; i < node_count; ++i) {
4083 const BVHNode &node = bvh_nodes[i];
4086 bvh_nodes_soa.aabb_mins[i] = node.aabb_min;
4087 bvh_nodes_soa.aabb_maxs[i] = node.aabb_max;
4088 bvh_nodes_soa.left_children[i] = node.left_child;
4089 bvh_nodes_soa.right_children[i] = node.right_child;
4092 bvh_nodes_soa.primitive_starts[i] = node.primitive_start;
4093 bvh_nodes_soa.primitive_counts[i] = node.primitive_count;
4094 bvh_nodes_soa.is_leaf_flags[i] = node.is_leaf ? 1 : 0;
4102void CollisionDetection::updatePrimitiveAABBCache(
uint uuid) {
4111 if (vertices.empty()) {
4116 vec3 aabb_min = vertices[0];
4117 vec3 aabb_max = vertices[0];
4119 for (
const vec3 &vertex: vertices) {
4120 aabb_min.
x = std::min(aabb_min.
x, vertex.x);
4121 aabb_min.
y = std::min(aabb_min.
y, vertex.y);
4122 aabb_min.
z = std::min(aabb_min.
z, vertex.z);
4124 aabb_max.
x = std::max(aabb_max.
x, vertex.x);
4125 aabb_max.
y = std::max(aabb_max.
y, vertex.y);
4126 aabb_max.
z = std::max(aabb_max.
z, vertex.z);
4130 primitive_aabbs_cache[uuid] = std::make_pair(aabb_min, aabb_max);
4132 }
catch (
const std::exception &e) {
4133 if (printmessages) {
4134 std::cerr <<
"Warning: Failed to cache AABB for primitive " << uuid <<
": " << e.what() << std::endl;
4139void CollisionDetection::optimizedRebuildBVH(
const std::set<uint> &final_geometry) {
4141 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
4144 for (
uint uuid: final_geometry) {
4145 if (primitive_aabbs_cache.find(uuid) == primitive_aabbs_cache.end()) {
4146 updatePrimitiveAABBCache(uuid);
4150 if (printmessages) {
4151 std::cout <<
"Optimized rebuild with " << final_primitives.size() <<
" primitives (using cached AABBs)" << std::endl;
4158 last_bvh_geometry = final_geometry;
4166 tree_based_bvh_enabled =
true;
4167 tree_isolation_distance = isolation_distance;
4171 tree_based_bvh_enabled =
false;
4172 tree_bvh_map.clear();
4173 object_to_tree_map.clear();
4177 return tree_based_bvh_enabled;
4181 if (static_obstacle_primitives.empty() || obstacle_spatial_grid_initialized) {
4186 obstacle_spatial_grid.cell_size = 20.0f;
4187 obstacle_spatial_grid.grid_cells.clear();
4190 for (
uint obstacle_prim: static_obstacle_primitives) {
4194 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4196 int64_t grid_key = obstacle_spatial_grid.getGridKey(prim_center.
x, prim_center.
y);
4197 obstacle_spatial_grid.grid_cells[grid_key].push_back(obstacle_prim);
4201 obstacle_spatial_grid_initialized =
true;
4204std::vector<uint> CollisionDetection::ObstacleSpatialGrid::getRelevantObstacles(
const helios::vec3 &position,
float radius)
const {
4205 std::vector<uint> relevant_obstacles;
4208 int32_t min_grid_x =
static_cast<int32_t
>(std::floor((position.
x - radius) / cell_size));
4209 int32_t max_grid_x =
static_cast<int32_t
>(std::floor((position.
x + radius) / cell_size));
4210 int32_t min_grid_y =
static_cast<int32_t
>(std::floor((position.
y - radius) / cell_size));
4211 int32_t max_grid_y =
static_cast<int32_t
>(std::floor((position.
y + radius) / cell_size));
4214 for (int32_t grid_x = min_grid_x; grid_x <= max_grid_x; ++grid_x) {
4215 for (int32_t grid_y = min_grid_y; grid_y <= max_grid_y; ++grid_y) {
4216 int64_t grid_key = (
static_cast<int64_t
>(grid_x) << 32) |
static_cast<uint32_t
>(grid_y);
4218 auto cell_it = grid_cells.find(grid_key);
4219 if (cell_it != grid_cells.end()) {
4221 relevant_obstacles.insert(relevant_obstacles.end(), cell_it->second.begin(), cell_it->second.end());
4226 return relevant_obstacles;
4230 if (!tree_based_bvh_enabled) {
4231 if (printmessages) {
4232 std::cout <<
"WARNING: Tree registration ignored - tree-based BVH not enabled" << std::endl;
4238 vec3 tree_center(0, 0, 0);
4239 vec3 aabb_min(1e30f, 1e30f, 1e30f);
4240 vec3 aabb_max(-1e30f, -1e30f, -1e30f);
4242 for (
uint prim_uuid: tree_primitives) {
4244 vec3 prim_min, prim_max;
4247 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
4248 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
4249 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
4251 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
4252 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
4253 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
4257 tree_center = (aabb_min + aabb_max) * 0.5f;
4258 float tree_radius = (aabb_max - aabb_min).magnitude() * 0.5f;
4261 TreeBVH &tree_bvh = tree_bvh_map[tree_object_id];
4262 tree_bvh.tree_object_id = tree_object_id;
4263 tree_bvh.tree_center = tree_center;
4264 tree_bvh.tree_radius = tree_radius;
4267 for (
uint prim_uuid: tree_primitives) {
4268 object_to_tree_map[prim_uuid] = tree_object_id;
4273 static_obstacle_primitives.clear();
4276 for (
uint prim_uuid: obstacle_primitives) {
4278 static_obstacle_primitives.push_back(prim_uuid);
4283 obstacle_spatial_grid_initialized =
false;
4288 std::vector<uint> relevant_geometry;
4290 if (!tree_based_bvh_enabled) {
4292 return relevant_geometry;
4296 const float MAX_STATIC_OBSTACLE_DISTANCE = max_distance;
4298 if (obstacle_spatial_grid_initialized && !static_obstacle_primitives.empty()) {
4300 std::vector<uint> candidate_obstacles = obstacle_spatial_grid.getRelevantObstacles(query_position, MAX_STATIC_OBSTACLE_DISTANCE);
4304 for (
uint static_prim: candidate_obstacles) {
4308 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4309 float distance = (query_position - prim_center).magnitude();
4310 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4311 relevant_geometry.push_back(static_prim);
4318 for (
uint static_prim: static_obstacle_primitives) {
4322 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4323 float distance = (query_position - prim_center).magnitude();
4324 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4325 relevant_geometry.push_back(static_prim);
4332 uint source_tree_id = 0;
4333 if (!query_primitives.empty()) {
4335 auto it = object_to_tree_map.find(query_primitives[0]);
4336 if (it != object_to_tree_map.end()) {
4337 source_tree_id = it->second;
4342 if (source_tree_id == 0) {
4343 float min_distance = 1e30f;
4344 for (
const auto &tree_pair: tree_bvh_map) {
4345 const TreeBVH &tree = tree_pair.second;
4346 float distance = (query_position - tree.tree_center).magnitude();
4347 if (distance < min_distance) {
4348 min_distance = distance;
4349 source_tree_id = tree.tree_object_id;
4355 for (
const auto &tree_pair: tree_bvh_map) {
4356 const TreeBVH &tree = tree_pair.second;
4357 uint tree_id = tree_pair.first;
4359 if (tree_id == source_tree_id) {
4361 for (
uint prim_uuid: tree.primitive_indices) {
4363 relevant_geometry.push_back(prim_uuid);
4368 float distance = (query_position - tree.tree_center).magnitude();
4369 float interaction_threshold = tree_isolation_distance + tree.tree_radius;
4371 if (distance < interaction_threshold) {
4373 for (
uint prim_uuid: tree.primitive_indices) {
4375 relevant_geometry.push_back(prim_uuid);
4382 return relevant_geometry;