29#elif defined(__SSE4_1__)
31#elif defined(__SSE2__)
35#ifdef HELIOS_CUDA_AVAILABLE
36#include <cuda_runtime.h>
39using namespace helios;
41#ifdef HELIOS_CUDA_AVAILABLE
52void launchBVHTraversal(
void *h_nodes,
int node_count,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_query_aabb_min,
float *h_query_aabb_max,
int num_queries,
53 unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_query);
54bool launchVoxelRayPathLengths(
int num_rays,
float *h_ray_origins,
float *h_ray_directions,
float grid_center_x,
float grid_center_y,
float grid_center_z,
float grid_size_x,
float grid_size_y,
float grid_size_z,
int grid_divisions_x,
55 int grid_divisions_y,
int grid_divisions_z,
int primitive_count,
int *h_voxel_ray_counts,
float *h_voxel_path_lengths,
int *h_voxel_transmitted,
int *h_voxel_hit_before,
int *h_voxel_hit_after,
int *h_voxel_hit_inside);
57void launchWarpEfficientBVH(
void *h_bvh_soa_gpu,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_ray_origins,
float *h_ray_directions,
float *h_ray_max_distances,
58 int num_rays,
unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_ray);
63 return make_float3(v.
x, v.
y, v.
z);
69 if (a_context ==
nullptr) {
76#ifdef HELIOS_CUDA_AVAILABLE
79 gpu_acceleration_enabled =
false;
83 d_bvh_nodes =
nullptr;
84 d_primitive_indices =
nullptr;
85 d_primitive_types =
nullptr;
86 d_primitive_vertices =
nullptr;
87 d_vertex_offsets =
nullptr;
88 d_mask_data =
nullptr;
89 d_mask_offsets =
nullptr;
90 d_mask_sizes =
nullptr;
94 d_gpu_has_masks =
false;
96 d_gpu_primitive_count = 0;
97 d_gpu_total_vertex_count = 0;
98 gpu_memory_allocated =
false;
103 automatic_bvh_rebuilds =
true;
106 hierarchical_bvh_enabled =
false;
107 static_bvh_valid =
false;
110 tree_based_bvh_enabled =
false;
111 tree_isolation_distance = 5.0f;
112 obstacle_spatial_grid_initialized =
false;
116 static bool openmp_warning_issued =
false;
117 if (printmessages && !openmp_warning_issued) {
118 std::cout <<
"WARNING (CollisionDetection): OpenMP not available. Using serial CPU implementation. "
119 <<
"Performance will be significantly slower. Consider installing OpenMP for parallel execution." << std::endl;
120 openmp_warning_issued =
true;
130 voxel_data_initialized =
false;
134 use_flat_arrays =
false;
137 max_collision_distance = 10.0f;
141#ifdef HELIOS_CUDA_AVAILABLE
147 return findCollisions(std::vector<uint>{UUID}, allow_spatial_culling);
156 warnings.
addWarning(
"no_uuids_provided",
"No UUIDs provided");
157 warnings.
report(std::cerr);
162 std::vector<uint> valid_UUIDs;
163 for (
uint uuid: UUIDs) {
164 if (
context->doesPrimitiveExist(uuid)) {
165 valid_UUIDs.push_back(uuid);
167 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Invalid UUID " + std::to_string(uuid) +
" provided");
174 std::vector<uint> all_collisions;
176 for (
uint UUID: valid_UUIDs) {
179 if (!
context->doesPrimitiveExist(UUID)) {
182 vec3 aabb_min, aabb_max;
183 context->getPrimitiveBoundingBox(UUID, aabb_min, aabb_max);
185 std::vector<uint> collisions;
187#ifdef HELIOS_CUDA_AVAILABLE
188 if (gpu_acceleration_enabled && gpu_memory_allocated) {
189 collisions = traverseBVH_GPU(aabb_min, aabb_max);
191 collisions = traverseBVH_CPU(aabb_min, aabb_max);
194 collisions = traverseBVH_CPU(aabb_min, aabb_max);
198 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
201 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
205 std::sort(all_collisions.begin(), all_collisions.end());
206 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
208 return all_collisions;
216 if (primitive_UUIDs.empty() && object_IDs.empty()) {
217 warnings.
addWarning(
"no_inputs_provided",
"No UUIDs or object IDs provided");
218 warnings.
report(std::cerr);
223 std::vector<uint> all_test_UUIDs = primitive_UUIDs;
225 for (
uint ObjID: object_IDs) {
226 if (!
context->doesObjectExist(ObjID)) {
227 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Object ID " + std::to_string(ObjID) +
" does not exist");
230 std::vector<uint> object_UUIDs =
context->getObjectPrimitiveUUIDs(ObjID);
231 all_test_UUIDs.insert(all_test_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
237std::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) {
242 if (query_UUIDs.empty() && query_object_IDs.empty()) {
243 warnings.
addWarning(
"no_query_inputs",
"No query UUIDs or object IDs provided");
244 warnings.
report(std::cerr);
249 std::vector<uint> all_query_UUIDs = query_UUIDs;
251 for (
uint ObjID: query_object_IDs) {
252 if (!
context->doesObjectExist(ObjID)) {
253 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Query object ID " + std::to_string(ObjID) +
" does not exist");
256 std::vector<uint> object_UUIDs =
context->getObjectPrimitiveUUIDs(ObjID);
257 all_query_UUIDs.insert(all_query_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
261 if (!validateUUIDs(all_query_UUIDs)) {
262 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid query UUIDs provided");
267 if (target_UUIDs.empty() && target_object_IDs.empty()) {
272 std::vector<uint> all_target_UUIDs = target_UUIDs;
274 for (
uint ObjID: target_object_IDs) {
275 if (!
context->doesObjectExist(ObjID)) {
276 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): Target object ID " + std::to_string(ObjID) +
" does not exist");
279 std::vector<uint> object_UUIDs =
context->getObjectPrimitiveUUIDs(ObjID);
280 all_target_UUIDs.insert(all_target_UUIDs.end(), object_UUIDs.begin(), object_UUIDs.end());
284 if (tree_based_bvh_enabled && allow_spatial_culling && !all_query_UUIDs.empty()) {
287 if (!all_query_UUIDs.empty()) {
290 context->getPrimitiveBoundingBox(all_query_UUIDs[0], min_corner, max_corner);
291 query_center = (min_corner + max_corner) * 0.5f;
297 all_target_UUIDs = effective_targets;
301 if (!all_target_UUIDs.empty() && !validateUUIDs(all_target_UUIDs)) {
302 helios_runtime_error(
"ERROR (CollisionDetection::findCollisions): One or more invalid target UUIDs provided");
306 if (!all_target_UUIDs.empty()) {
312 std::vector<uint> all_collisions;
314 for (
uint UUID: all_query_UUIDs) {
317 if (!
context->doesPrimitiveExist(UUID)) {
320 vec3 aabb_min, aabb_max;
321 context->getPrimitiveBoundingBox(UUID, aabb_min, aabb_max);
323 std::vector<uint> collisions;
325#ifdef HELIOS_CUDA_AVAILABLE
326 if (gpu_acceleration_enabled && gpu_memory_allocated) {
327 collisions = traverseBVH_GPU(aabb_min, aabb_max);
329 collisions = traverseBVH_CPU(aabb_min, aabb_max);
332 collisions = traverseBVH_CPU(aabb_min, aabb_max);
336 collisions.erase(std::remove(collisions.begin(), collisions.end(), UUID), collisions.end());
339 all_collisions.insert(all_collisions.end(), collisions.begin(), collisions.end());
343 std::sort(all_collisions.begin(), all_collisions.end());
344 all_collisions.erase(std::unique(all_collisions.begin(), all_collisions.end()), all_collisions.end());
346 warnings.
report(std::cerr);
347 return all_collisions;
356 std::vector<uint> primitives_to_include;
360 primitives_to_include =
context->getAllUUIDs();
362 primitives_to_include = UUIDs;
366 if (primitives_to_include.empty()) {
367 warnings.
addWarning(
"no_primitives_for_bvh",
"No primitives found to build BVH");
368 warnings.
report(std::cerr);
373 std::vector<uint> valid_primitives;
374 if (!UUIDs.empty()) {
376 for (
uint uuid: primitives_to_include) {
377 if (
context->doesPrimitiveExist(uuid)) {
378 valid_primitives.push_back(uuid);
380 helios_runtime_error(
"ERROR (CollisionDetection::buildBVH): Invalid UUID " + std::to_string(uuid) +
" provided");
385 for (
uint uuid: primitives_to_include) {
386 if (
context->doesPrimitiveExist(uuid)) {
387 valid_primitives.push_back(uuid);
389 warnings.
addWarning(
"invalid_uuid_skipped",
"Skipping invalid UUID " + std::to_string(uuid));
393 if (valid_primitives.empty()) {
394 warnings.
addWarning(
"no_valid_primitives_after_filtering",
"No valid primitives found after filtering");
395 warnings.
report(std::cerr);
400 primitives_to_include = valid_primitives;
403 std::set<uint> new_primitive_set(primitives_to_include.begin(), primitives_to_include.end());
404 std::set<uint> old_primitive_set(primitive_indices.begin(), primitive_indices.end());
406 bool primitive_set_changed = (new_primitive_set != old_primitive_set);
408 if (primitive_set_changed) {
410 primitive_cache.clear();
415 primitive_indices.clear();
418 primitive_indices = primitives_to_include;
422 size_t max_nodes = std::max(
size_t(1), 2 * primitives_to_include.size());
424 bvh_nodes.resize(max_nodes);
425 next_available_node_index = 1;
429 std::unordered_set<uint> current_primitives(primitives_to_include.begin(), primitives_to_include.end());
432 auto cache_it = primitive_aabbs_cache.begin();
433 while (cache_it != primitive_aabbs_cache.end()) {
434 if (current_primitives.find(cache_it->first) == current_primitives.end()) {
435 cache_it = primitive_aabbs_cache.erase(cache_it);
442 for (
uint UUID: primitives_to_include) {
443 if (!
context->doesPrimitiveExist(UUID)) {
448 bool needs_update = (primitive_aabbs_cache.find(UUID) == primitive_aabbs_cache.end()) || (dirty_primitive_cache.find(UUID) != dirty_primitive_cache.end());
451 vec3 aabb_min, aabb_max;
452 context->getPrimitiveBoundingBox(UUID, aabb_min, aabb_max);
453 primitive_aabbs_cache[UUID] = {aabb_min, aabb_max};
454 dirty_primitive_cache.erase(UUID);
459 buildBVHRecursive(0, 0, primitive_indices.size(), 0);
462 bvh_nodes.resize(next_available_node_index);
466 bvh_nodes_soa.clear();
467 bvh_nodes_soa.node_count = 0;
472 primitive_cache_dense.clear();
475#ifdef HELIOS_CUDA_AVAILABLE
476 if (gpu_acceleration_enabled) {
483 std::vector<uint> context_deleted_uuids =
context->getDeletedUUIDs();
487 last_processed_uuids.clear();
488 last_processed_uuids.insert(primitives_to_include.begin(), primitives_to_include.end());
490 last_processed_deleted_uuids.clear();
491 last_processed_deleted_uuids.insert(context_deleted_uuids.begin(), context_deleted_uuids.end());
494 last_bvh_geometry.clear();
495 last_bvh_geometry.insert(primitives_to_include.begin(), primitives_to_include.end());
500 warnings.
report(std::cerr);
509 automatic_bvh_rebuilds =
false;
513 automatic_bvh_rebuilds =
true;
517 hierarchical_bvh_enabled =
true;
518 static_bvh_valid =
false;
522 hierarchical_bvh_enabled =
false;
524 static_bvh_nodes.clear();
525 static_bvh_primitives.clear();
526 static_bvh_valid =
false;
527 last_static_bvh_geometry.clear();
530void CollisionDetection::updateHierarchicalBVH(
const std::set<uint> &requested_geometry,
bool force_rebuild) {
533 if (!static_bvh_valid || force_rebuild || static_geometry_cache != last_static_bvh_geometry) {
538 std::vector<uint> dynamic_geometry;
539 for (
uint uuid: requested_geometry) {
540 if (static_geometry_cache.find(uuid) == static_geometry_cache.end()) {
541 dynamic_geometry.push_back(uuid);
547 if (!dynamic_geometry.empty()) {
552 primitive_indices.clear();
556 last_bvh_geometry = requested_geometry;
562 if (static_geometry_cache.empty()) {
563 static_bvh_nodes.clear();
564 static_bvh_primitives.clear();
565 static_bvh_valid =
false;
569 std::vector<uint> static_primitives(static_geometry_cache.begin(), static_geometry_cache.end());
574 std::vector<BVHNode> temp_nodes;
575 std::vector<uint> temp_primitives;
578 std::swap(bvh_nodes, temp_nodes);
579 std::swap(primitive_indices, temp_primitives);
585 static_bvh_nodes = bvh_nodes;
586 static_bvh_primitives = primitive_indices;
587 std::swap(bvh_nodes, temp_nodes);
588 std::swap(primitive_indices, temp_primitives);
590 static_bvh_valid =
true;
591 last_static_bvh_geometry = static_geometry_cache;
596 std::set<uint> requested_geometry(UUIDs.begin(), UUIDs.end());
599 bool geometry_changed = (requested_geometry != last_bvh_geometry) || bvh_dirty;
601 if (!geometry_changed && !force_rebuild) {
606 if (hierarchical_bvh_enabled) {
607 updateHierarchicalBVH(requested_geometry, force_rebuild);
612 if (force_rebuild || bvh_nodes.empty()) {
617 std::set<uint> added_geometry, removed_geometry;
620 std::set_difference(requested_geometry.begin(), requested_geometry.end(), last_bvh_geometry.begin(), last_bvh_geometry.end(), std::inserter(added_geometry, added_geometry.begin()));
623 std::set_difference(last_bvh_geometry.begin(), last_bvh_geometry.end(), requested_geometry.begin(), requested_geometry.end(), std::inserter(removed_geometry, removed_geometry.begin()));
626 size_t total_change = added_geometry.size() + removed_geometry.size();
627 size_t current_size = std::max(last_bvh_geometry.size(), requested_geometry.size());
631 bool mostly_additions = (removed_geometry.size() < added_geometry.size() * 0.1f);
632 float change_threshold = mostly_additions ? 0.5f : 0.2f;
634 if (current_size == 0 || (
float(total_change) /
float(current_size)) > change_threshold) {
638 incrementalUpdateBVH(added_geometry, removed_geometry, requested_geometry);
643 last_bvh_geometry = requested_geometry;
649 static_geometry_cache.clear();
650 static_geometry_cache.insert(UUIDs.begin(), UUIDs.end());
653void CollisionDetection::ensureBVHCurrent() {
655 if (!automatic_bvh_rebuilds) {
660 if (bvh_nodes.empty()) {
662 std::cout <<
"Building initial BVH..." << std::endl;
670 std::vector<uint> context_dirty_uuids =
context->getDirtyUUIDs(
false);
671 std::vector<uint> context_deleted_uuids =
context->getDeletedUUIDs();
674 std::set<uint> current_dirty(context_dirty_uuids.begin(), context_dirty_uuids.end());
675 std::set<uint> current_deleted(context_deleted_uuids.begin(), context_deleted_uuids.end());
678 bool has_new_dirty =
false;
679 bool has_new_deleted =
false;
682 for (
uint uuid: current_dirty) {
683 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
684 has_new_dirty =
true;
690 for (
uint uuid: current_deleted) {
691 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
692 has_new_deleted =
true;
698 if (has_new_dirty || has_new_deleted) {
700 std::cout <<
"Geometry has changed since last BVH build, rebuilding..." << std::endl;
711 if (bvh_nodes.empty()) {
716 std::vector<uint> all_context_uuids =
context->getAllUUIDs();
717 for (
uint uuid: all_context_uuids) {
718 if (last_processed_uuids.find(uuid) == last_processed_uuids.end()) {
724 std::vector<uint> context_deleted_uuids =
context->getDeletedUUIDs();
725 for (
uint uuid: context_deleted_uuids) {
726 if (last_processed_deleted_uuids.find(uuid) == last_processed_deleted_uuids.end()) {
732 for (
uint uuid: primitive_indices) {
733 if (!
context->doesPrimitiveExist(uuid)) {
742#ifdef HELIOS_CUDA_AVAILABLE
745 std::cerr <<
"WARNING: GPU acceleration requested but no usable GPU is available (no CUDA device or HELIOS_NO_GPU is set). Using CPU-only mode." << std::endl;
747 gpu_acceleration_enabled =
false;
750 gpu_acceleration_enabled =
true;
751 if (!bvh_nodes.empty()) {
756 std::cerr <<
"WARNING: GPU acceleration requested but CUDA not available. Ignoring request." << std::endl;
762 gpu_acceleration_enabled =
false;
763#ifdef HELIOS_CUDA_AVAILABLE
769 return gpu_acceleration_enabled;
773 static bool checked =
false;
774 static bool available =
false;
780#ifdef HELIOS_CUDA_AVAILABLE
782 const char *no_gpu = std::getenv(
"HELIOS_NO_GPU");
783 if (no_gpu && std::string(no_gpu) !=
"0") {
788 cudaError_t err = cudaGetDeviceCount(&deviceCount);
789 available = (err == cudaSuccess && deviceCount > 0);
797 printmessages =
false;
801 printmessages =
true;
809 return primitive_indices.size();
814 node_count = bvh_nodes.size();
819 std::function<void(
uint,
size_t)> traverse = [&](
uint node_idx,
size_t depth) {
820 if (node_idx >= bvh_nodes.size())
823 const BVHNode &node = bvh_nodes[node_idx];
824 max_depth = std::max(max_depth, depth);
838 if (!bvh_nodes.empty()) {
843void CollisionDetection::calculateAABB(
const std::vector<uint> &primitives,
vec3 &aabb_min,
vec3 &aabb_max)
const {
845 if (primitives.empty()) {
852 size_t first_valid = 0;
853 while (first_valid < primitives.size() && !
context->doesPrimitiveExist(primitives[first_valid])) {
856 if (first_valid >= primitives.size()) {
864 context->getPrimitiveBoundingBox(primitives[first_valid], aabb_min, aabb_max);
867 for (
size_t i = first_valid + 1; i < primitives.size(); i++) {
868 if (!
context->doesPrimitiveExist(primitives[i])) {
871 vec3 prim_min, prim_max;
872 context->getPrimitiveBoundingBox(primitives[i], prim_min, prim_max);
874 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
875 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
876 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
878 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
879 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
880 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
884void CollisionDetection::buildBVHRecursive(
uint node_index,
size_t primitive_start,
size_t primitive_count,
int depth) {
887 if (node_index >= bvh_nodes.size()) {
888 throw std::runtime_error(
"CollisionDetection: BVH recursive access exceeded pre-allocated capacity");
892 if (primitive_start + primitive_count > primitive_indices.size()) {
893 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(" +
894 std::to_string(primitive_indices.size()) +
")");
897 BVHNode &node = bvh_nodes[node_index];
900 if (primitive_count == 0) {
905 uint first_uuid = primitive_indices[primitive_start];
906 auto it = primitive_aabbs_cache.find(first_uuid);
907 if (it == primitive_aabbs_cache.end()) {
913 const auto &first_cached_aabb = it->second;
914 node.
aabb_min = first_cached_aabb.first;
915 node.
aabb_max = first_cached_aabb.second;
918 for (
size_t i = 1; i < primitive_count; i++) {
919 uint uuid = primitive_indices[primitive_start + i];
920 auto it = primitive_aabbs_cache.find(uuid);
921 if (it == primitive_aabbs_cache.end()) {
924 const auto &cached_aabb = it->second;
938 const int MAX_PRIMITIVES_PER_LEAF = 8;
939 const int MAX_DEPTH = 64;
941 if (primitive_count <=
static_cast<size_t>(MAX_PRIMITIVES_PER_LEAF) || depth >= MAX_DEPTH) {
944 node.primitive_start = primitive_start;
945 node.primitive_count = primitive_count;
956 constexpr int NUM_BINS = 16;
958 auto centroid_of = [&](
uint uuid,
vec3 &out) ->
bool {
959 auto it = primitive_aabbs_cache.find(uuid);
960 if (it == primitive_aabbs_cache.end()) {
963 out = (it->second.first + it->second.second) * 0.5f;
968 vec3 centroid_min =
make_vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
969 vec3 centroid_max =
make_vec3(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
970 for (
size_t i = 0; i < primitive_count; i++) {
972 if (!centroid_of(primitive_indices[primitive_start + i], c)) {
975 centroid_min.
x = std::min(centroid_min.
x, c.
x);
976 centroid_min.
y = std::min(centroid_min.
y, c.
y);
977 centroid_min.
z = std::min(centroid_min.
z, c.
z);
978 centroid_max.
x = std::max(centroid_max.
x, c.
x);
979 centroid_max.
y = std::max(centroid_max.
y, c.
y);
980 centroid_max.
z = std::max(centroid_max.
z, c.
z);
982 const vec3 centroid_extent = centroid_max - centroid_min;
984 auto surface_area = [](
const vec3 &mn,
const vec3 &mx) ->
float {
985 const vec3 d = mx - mn;
986 if (d.
x < 0.f || d.
y < 0.f || d.
z < 0.f) {
989 return 2.0f * (d.
x * d.
y + d.
y * d.
z + d.
z * d.
x);
993 float best_cost = std::numeric_limits<float>::max();
994 int best_bin_boundary = -1;
997 for (
int axis = 0; axis < 3; axis++) {
998 const float axis_extent = (axis == 0) ? centroid_extent.
x : (axis == 1) ? centroid_extent.y : centroid_extent.z;
999 if (axis_extent <= 0.f) {
1002 const float axis_min = (axis == 0) ? centroid_min.
x : (axis == 1) ? centroid_min.y : centroid_min.z;
1003 const float scale = float(NUM_BINS) / axis_extent;
1005 int bin_counts[NUM_BINS] = {0};
1006 vec3 bin_min[NUM_BINS];
1007 vec3 bin_max[NUM_BINS];
1008 for (
int b = 0; b < NUM_BINS; b++) {
1009 bin_min[b] =
make_vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
1010 bin_max[b] =
make_vec3(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
1014 for (
size_t i = 0; i < primitive_count; i++) {
1015 const uint uuid = primitive_indices[primitive_start + i];
1016 auto it = primitive_aabbs_cache.find(uuid);
1017 if (it == primitive_aabbs_cache.end()) {
1020 const vec3 c = (it->second.first + it->second.second) * 0.5f;
1021 const float c_axis = (axis == 0) ? c.
x : (axis == 1) ? c.y : c.z;
1022 int bin = int((c_axis - axis_min) * scale);
1025 if (bin >= NUM_BINS)
1028 const vec3 &pmin = it->second.first;
1029 const vec3 &pmax = it->second.second;
1030 bin_min[bin].
x = std::min(bin_min[bin].x, pmin.
x);
1031 bin_min[bin].
y = std::min(bin_min[bin].y, pmin.
y);
1032 bin_min[bin].
z = std::min(bin_min[bin].z, pmin.
z);
1033 bin_max[bin].
x = std::max(bin_max[bin].x, pmax.
x);
1034 bin_max[bin].
y = std::max(bin_max[bin].y, pmax.
y);
1035 bin_max[bin].
z = std::max(bin_max[bin].z, pmax.
z);
1039 int left_count[NUM_BINS];
1040 float left_area[NUM_BINS];
1041 vec3 acc_min =
make_vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
1042 vec3 acc_max =
make_vec3(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
1044 for (
int b = 0; b < NUM_BINS; b++) {
1045 if (bin_counts[b] > 0) {
1046 acc_min.
x = std::min(acc_min.
x, bin_min[b].x);
1047 acc_min.
y = std::min(acc_min.
y, bin_min[b].y);
1048 acc_min.
z = std::min(acc_min.
z, bin_min[b].z);
1049 acc_max.
x = std::max(acc_max.
x, bin_max[b].x);
1050 acc_max.
y = std::max(acc_max.
y, bin_max[b].y);
1051 acc_max.
z = std::max(acc_max.
z, bin_max[b].z);
1053 running += bin_counts[b];
1054 left_count[b] = running;
1055 left_area[b] = (running > 0) ? surface_area(acc_min, acc_max) : 0.f;
1058 acc_min =
make_vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
1059 acc_max =
make_vec3(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
1062 for (
int b = NUM_BINS - 1; b >= 1; b--) {
1063 if (bin_counts[b] > 0) {
1064 acc_min.
x = std::min(acc_min.
x, bin_min[b].x);
1065 acc_min.
y = std::min(acc_min.
y, bin_min[b].y);
1066 acc_min.
z = std::min(acc_min.
z, bin_min[b].z);
1067 acc_max.
x = std::max(acc_max.
x, bin_max[b].x);
1068 acc_max.
y = std::max(acc_max.
y, bin_max[b].y);
1069 acc_max.
z = std::max(acc_max.
z, bin_max[b].z);
1071 running += bin_counts[b];
1072 const int right_count = running;
1073 const float right_area = (right_count > 0) ? surface_area(acc_min, acc_max) : 0.f;
1074 const int lc = left_count[b - 1];
1075 if (lc == 0 || right_count == 0) {
1078 const float cost = left_area[b - 1] * float(lc) + right_area * float(right_count);
1079 if (cost < best_cost) {
1082 best_bin_boundary = b - 1;
1088 if (split_axis < 0) {
1092 int median_axis = 0;
1093 if (extent.
y > extent.
x)
1095 if (extent.
z > (median_axis == 0 ? extent.
x : extent.y))
1097 std::sort(primitive_indices.begin() + primitive_start, primitive_indices.begin() + primitive_start + primitive_count, [&](
uint a,
uint b) {
1099 const bool oka = centroid_of(a, ca);
1100 const bool okb = centroid_of(b, cb);
1104 const float va = (median_axis == 0) ? ca.x : (median_axis == 1) ? ca.y : ca.z;
1105 const float vb = (median_axis == 0) ? cb.x : (median_axis == 1) ? cb.y : cb.z;
1111 split_index = primitive_count / 2;
1116 const float axis_min = (split_axis == 0) ? centroid_min.x : (split_axis == 1) ? centroid_min.y : centroid_min.z;
1117 const float axis_extent = (split_axis == 0) ? centroid_extent.
x : (split_axis == 1) ? centroid_extent.y : centroid_extent.z;
1118 const float scale = float(NUM_BINS) / axis_extent;
1119 const int boundary = best_bin_boundary;
1120 auto mid = std::partition(primitive_indices.begin() + primitive_start, primitive_indices.begin() + primitive_start + primitive_count, [&](
uint uuid) {
1122 if (!centroid_of(uuid, c)) {
1125 const float c_axis = (split_axis == 0) ? c.
x : (split_axis == 1) ? c.y : c.z;
1126 int bin = int((c_axis - axis_min) * scale);
1129 if (bin >= NUM_BINS)
1131 return bin <= boundary;
1133 split_index = size_t(std::distance(primitive_indices.begin() + primitive_start, mid));
1135 if (split_index == 0 || split_index == primitive_count) {
1136 split_index = primitive_count / 2;
1141 uint left_child_index = next_available_node_index++;
1142 uint right_child_index = next_available_node_index++;
1145 if (right_child_index >= bvh_nodes.size()) {
1146 throw std::runtime_error(
"CollisionDetection: BVH node allocation exceeded pre-calculated capacity");
1150 BVHNode &updated_node = bvh_nodes[node_index];
1153 updated_node.is_leaf =
false;
1154 updated_node.primitive_start = 0;
1155 updated_node.primitive_count = 0;
1158 buildBVHRecursive(left_child_index, primitive_start, split_index, depth + 1);
1159 buildBVHRecursive(right_child_index, primitive_start + split_index, primitive_count - split_index, depth + 1);
1162std::vector<uint> CollisionDetection::traverseBVH_CPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
1164 std::vector<uint> results;
1166 if (bvh_nodes.empty()) {
1171 std::vector<uint> node_stack;
1172 node_stack.push_back(0);
1174 while (!node_stack.empty()) {
1175 uint node_idx = node_stack.back();
1176 node_stack.pop_back();
1178 if (node_idx >= bvh_nodes.size())
1181 const BVHNode &node = bvh_nodes[node_idx];
1184 if (!aabbIntersect(query_aabb_min, query_aabb_max, node.
aabb_min, node.
aabb_max)) {
1190 for (
uint i = 0; i < node.primitive_count; i++) {
1191 uint primitive_id = primitive_indices[node.primitive_start + i];
1194 if (!
context->doesPrimitiveExist(primitive_id)) {
1197 vec3 prim_min, prim_max;
1198 context->getPrimitiveBoundingBox(primitive_id, prim_min, prim_max);
1201 if (aabbIntersect(query_aabb_min, query_aabb_max, prim_min, prim_max)) {
1202 results.push_back(primitive_id);
1219#ifdef HELIOS_CUDA_AVAILABLE
1220std::vector<uint> CollisionDetection::traverseBVH_GPU(
const vec3 &query_aabb_min,
const vec3 &query_aabb_max) {
1221 if (!gpu_memory_allocated) {
1222 helios_runtime_error(
"ERROR: GPU traversal requested but GPU memory is not allocated. Call buildBVH() or transferBVHToGPU() first.");
1226 float query_min_array[3] = {query_aabb_min.
x, query_aabb_min.
y, query_aabb_min.
z};
1227 float query_max_array[3] = {query_aabb_max.
x, query_aabb_max.
y, query_aabb_max.
z};
1230 std::vector<float> primitive_min_array(primitive_indices.size() * 3);
1231 std::vector<float> primitive_max_array(primitive_indices.size() * 3);
1233 for (
size_t i = 0; i < primitive_indices.size(); i++) {
1234 uint uuid = primitive_indices[i];
1235 auto it = primitive_aabbs_cache.find(uuid);
1236 if (it == primitive_aabbs_cache.end()) {
1239 const auto &cached_aabb = it->second;
1241 primitive_min_array[i * 3] = cached_aabb.first.x;
1242 primitive_min_array[i * 3 + 1] = cached_aabb.first.y;
1243 primitive_min_array[i * 3 + 2] = cached_aabb.first.z;
1245 primitive_max_array[i * 3] = cached_aabb.second.x;
1246 primitive_max_array[i * 3 + 1] = cached_aabb.second.y;
1247 primitive_max_array[i * 3 + 2] = cached_aabb.second.z;
1250 const int max_results = 1000;
1251 std::vector<unsigned int> results(max_results);
1252 unsigned int result_count = 0;
1255 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);
1258 std::vector<uint> final_results;
1259 for (
unsigned int i = 0; i < result_count; i++) {
1260 final_results.push_back(results[i]);
1263 return final_results;
1267bool CollisionDetection::aabbIntersect(
const vec3 &min1,
const vec3 &max1,
const vec3 &min2,
const vec3 &max2) {
1268 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);
1271bool CollisionDetection::rayAABBIntersect(
const vec3 &origin,
const vec3 &direction,
const vec3 &aabb_min,
const vec3 &aabb_max,
float &t_min,
float &t_max)
const {
1274 t_max = std::numeric_limits<float>::max();
1277 for (
int i = 0; i < 3; i++) {
1278 float dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
1279 float orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
1280 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1281 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1283 if (std::abs(dir_component) < 1e-9f) {
1285 if (orig_component < min_component || orig_component > max_component) {
1290 float t1 = (min_component - orig_component) / dir_component;
1291 float t2 = (max_component - orig_component) / dir_component;
1299 t_min = std::max(t_min, t1);
1300 t_max = std::min(t_max, t2);
1303 if (t_min > t_max) {
1310 return t_max >= 0.0f;
1313bool CollisionDetection::coneAABBIntersect(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1315 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) {
1320 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1321 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1322 float box_radius = box_half_extents.
magnitude();
1325 if (cone.height <= 0.0f) {
1327 for (
int i = 0; i < 8; i++) {
1328 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);
1331 vec3 apex_to_corner = corner - cone.apex;
1332 float distance_along_axis = apex_to_corner * cone.axis;
1335 if (distance_along_axis > 0) {
1337 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1338 if (cos_angle >= cosf(cone.half_angle)) {
1347 float t_max = std::numeric_limits<float>::max();
1349 for (
int i = 0; i < 3; i++) {
1350 float axis_component = (i == 0) ? cone.axis.x : (i == 1) ? cone.axis.y : cone.axis.z;
1351 float apex_component = (i == 0) ? cone.apex.x : (i == 1) ? cone.apex.y : cone.apex.z;
1352 float min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
1353 float max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
1355 if (std::abs(axis_component) < 1e-6f) {
1357 if (apex_component < min_component || apex_component > max_component) {
1362 float t1 = (min_component - apex_component) / axis_component;
1363 float t2 = (max_component - apex_component) / axis_component;
1368 t_min = std::max(t_min, t1);
1369 t_max = std::min(t_max, t2);
1371 if (t_min > t_max) {
1379 if (t_min >= 0 && t_max >= 0) {
1382 float t_check = std::max(0.0f, t_min);
1383 vec3 axis_point = cone.apex + cone.axis * t_check;
1386 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)));
1389 vec3 apex_to_point = closest_in_box - cone.apex;
1390 float distance_along_axis = apex_to_point * cone.axis;
1392 if (distance_along_axis > 0) {
1393 float distance_to_point = apex_to_point.
magnitude();
1394 if (distance_to_point > 0) {
1395 float cos_angle = distance_along_axis / distance_to_point;
1396 if (cos_angle >= cosf(cone.half_angle)) {
1405 for (
int i = 0; i < 8; i++) {
1406 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);
1409 vec3 apex_to_corner = corner - cone.apex;
1410 float distance_along_axis = apex_to_corner * cone.axis;
1413 if (distance_along_axis > 0 && distance_along_axis <= cone.height) {
1415 float cos_angle = distance_along_axis / apex_to_corner.
magnitude();
1416 if (cos_angle >= cosf(cone.half_angle)) {
1423 vec3 base_center = cone.apex + cone.axis * cone.height;
1424 float base_radius = cone.height * tanf(cone.half_angle);
1427 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)));
1429 float dist_sq = (closest_point - base_center).magnitude();
1430 if (dist_sq <= base_radius) {
1440bool CollisionDetection::coneAABBIntersectFast(
const Cone &cone,
const vec3 &aabb_min,
const vec3 &aabb_max) {
1445 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) {
1450 vec3 box_center = 0.5f * (aabb_min + aabb_max);
1451 vec3 apex_to_center = box_center - cone.apex;
1452 float distance_along_axis = apex_to_center * cone.axis;
1454 if (distance_along_axis <= 0.0f) {
1459 if (cone.height > 0.0f && distance_along_axis > cone.height) {
1461 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1462 float box_radius = box_half_extents.
magnitude();
1463 if (distance_along_axis - box_radius > cone.height) {
1469 float max_distance = (cone.height > 0.0f) ? cone.height : distance_along_axis;
1470 float max_radius_at_distance = max_distance * tanf(cone.half_angle);
1473 vec3 axis_point = cone.apex + cone.axis * distance_along_axis;
1474 float distance_from_axis = (box_center - axis_point).magnitude();
1477 vec3 box_half_extents = 0.5f * (aabb_max - aabb_min);
1478 float box_radius = box_half_extents.
magnitude();
1480 if (distance_from_axis > max_radius_at_distance + box_radius) {
1486 return coneAABBIntersect(cone, aabb_min, aabb_max);
1491int CollisionDetection::calculateOptimalBinCount(
float cone_half_angle,
int geometry_count) {
1493 float base_resolution =
M_PI / 180.0f;
1494 float cone_solid_angle = 2.0f *
M_PI * (1.0f - cosf(cone_half_angle));
1495 int optimal_bins = (int) (cone_solid_angle / (base_resolution * base_resolution));
1499 int max_bins = std::min(1024, geometry_count * 4);
1501 return std::clamp(optimal_bins, min_bins, max_bins);
1504std::vector<uint> CollisionDetection::filterPrimitivesParallel(
const Cone &cone,
const std::vector<uint> &primitive_uuids) {
1505 std::vector<uint> filtered_uuids;
1507 if (primitive_uuids.empty()) {
1508 return filtered_uuids;
1512 filtered_uuids.reserve(primitive_uuids.size() / 10);
1516 const int num_threads = omp_get_max_threads();
1517 std::vector<std::vector<uint>> thread_results(num_threads);
1520 for (
int i = 0; i < num_threads; i++) {
1521 thread_results[i].reserve(primitive_uuids.size() / (num_threads * 10));
1527 int thread_id = omp_get_thread_num();
1528 std::vector<uint> &local_results = thread_results[thread_id];
1530#pragma omp for nowait
1531 for (
int i = 0; i < static_cast<int>(primitive_uuids.size()); i++) {
1532 uint uuid = primitive_uuids[i];
1535 if (
context->doesPrimitiveExist(uuid)) {
1536 std::vector<vec3> vertices =
context->getPrimitiveVertices(uuid);
1537 if (!vertices.empty()) {
1539 vec3 aabb_min = vertices[0];
1540 vec3 aabb_max = vertices[0];
1541 for (
const vec3 &vertex: vertices) {
1542 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));
1543 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));
1547 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1548 local_results.push_back(uuid);
1556 size_t total_count = 0;
1557 for (
const auto &thread_result: thread_results) {
1558 total_count += thread_result.size();
1561 filtered_uuids.reserve(total_count);
1562 for (
const auto &thread_result: thread_results) {
1563 filtered_uuids.insert(filtered_uuids.end(), thread_result.begin(), thread_result.end());
1567 for (
size_t i = 0; i < primitive_uuids.size(); i++) {
1568 uint uuid = primitive_uuids[i];
1571 if (
context->doesPrimitiveExist(uuid)) {
1572 std::vector<vec3> vertices =
context->getPrimitiveVertices(uuid);
1573 if (!vertices.empty()) {
1575 vec3 aabb_min = vertices[0];
1576 vec3 aabb_max = vertices[0];
1577 for (
const vec3 &vertex: vertices) {
1578 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));
1579 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));
1583 if (coneAABBIntersectFast(cone, aabb_min, aabb_max)) {
1584 filtered_uuids.push_back(uuid);
1591 return filtered_uuids;
1594float CollisionDetection::cartesianToSphericalCone(
const vec3 &vector,
const vec3 &cone_axis,
float &theta,
float &phi) {
1596 if (distance < 1e-6f) {
1602 vec3 normalized_vector = vector / distance;
1605 float cos_phi = normalized_vector * cone_axis;
1606 phi = acosf(std::clamp(cos_phi, -1.0f, 1.0f));
1616 vec3 projected = normalized_vector - cone_axis * cos_phi;
1619 float cos_theta = projected * right;
1620 float sin_theta = projected * forward;
1621 theta = atan2f(sin_theta, cos_theta);
1623 theta += 2.0f *
M_PI;
1631bool CollisionDetection::sphericalCoordsToBinIndices(
float theta,
float phi,
const AngularBins &bins,
int &theta_bin,
int &phi_bin) {
1633 if (phi < 0.0f || theta < 0.0f || theta >= 2.0f * M_PI) {
1638 theta_bin = (int) (theta * bins.theta_divisions / (2.0f * M_PI));
1639 phi_bin = (int) (phi * bins.phi_divisions / M_PI);
1642 theta_bin = std::clamp(theta_bin, 0, bins.theta_divisions - 1);
1643 phi_bin = std::clamp(phi_bin, 0, bins.phi_divisions - 1);
1648void CollisionDetection::projectGeometryToBins(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1651 const int PARALLEL_THRESHOLD = 500;
1653 if (filtered_uuids.size() > PARALLEL_THRESHOLD) {
1656 const int num_threads = omp_get_max_threads();
1657 std::vector<AngularBins> thread_bins(num_threads, AngularBins(bins.theta_divisions, bins.phi_divisions));
1661 int thread_id = omp_get_thread_num();
1662 AngularBins &local_bins = thread_bins[thread_id];
1664#pragma omp for nowait
1665 for (
int i = 0; i < static_cast<int>(filtered_uuids.size()); i++) {
1666 uint uuid = filtered_uuids[i];
1668 if (
context->doesPrimitiveExist(uuid)) {
1669 std::vector<vec3> vertices =
context->getPrimitiveVertices(uuid);
1672 for (
const vec3 &vertex: vertices) {
1673 vec3 apex_to_vertex = vertex - cone.apex;
1674 float distance = apex_to_vertex.
magnitude();
1676 if (distance > 1e-6f) {
1678 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1681 if (phi <= cone.half_angle) {
1682 int theta_bin, phi_bin;
1683 if (sphericalCoordsToBinIndices(theta, phi, local_bins, theta_bin, phi_bin)) {
1684 local_bins.setCovered(theta_bin, phi_bin, distance);
1694 for (
const auto &thread_bin: thread_bins) {
1695 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1696 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1697 if (thread_bin.isCovered(theta, phi)) {
1698 int index = theta * bins.phi_divisions + phi;
1699 float thread_depth = thread_bin.depth_values[index];
1700 bins.setCovered(theta, phi, thread_depth);
1707 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1711 projectGeometryToBinsSerial(cone, filtered_uuids, bins);
1715void CollisionDetection::projectGeometryToBinsSerial(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins) {
1716 for (
uint uuid: filtered_uuids) {
1717 if (
context->doesPrimitiveExist(uuid)) {
1718 std::vector<vec3> vertices =
context->getPrimitiveVertices(uuid);
1721 for (
const vec3 &vertex: vertices) {
1722 vec3 apex_to_vertex = vertex - cone.apex;
1723 float distance = apex_to_vertex.
magnitude();
1725 if (distance > 1e-6f) {
1727 cartesianToSphericalCone(apex_to_vertex, cone.axis, theta, phi);
1730 if (phi <= cone.half_angle) {
1731 int theta_bin, phi_bin;
1732 if (sphericalCoordsToBinIndices(theta, phi, bins, theta_bin, phi_bin)) {
1733 bins.setCovered(theta_bin, phi_bin, distance);
1742std::vector<CollisionDetection::Gap> CollisionDetection::findGapsInCoverageMap(
const AngularBins &bins,
const Cone &cone) {
1743 std::vector<Gap> gaps;
1746 std::vector<std::vector<bool>> visited(bins.theta_divisions, std::vector<bool>(bins.phi_divisions,
false));
1748 for (
int theta = 0; theta < bins.theta_divisions; theta++) {
1749 for (
int phi = 0; phi < bins.phi_divisions; phi++) {
1750 if (!bins.isCovered(theta, phi) && !visited[theta][phi]) {
1752 Gap gap = floodFillGap(bins, theta, phi, visited, cone);
1755 const float MIN_GAP_SIZE_STERADIANS = 0.01f;
1756 if (gap.angular_size > MIN_GAP_SIZE_STERADIANS) {
1757 gaps.push_back(gap);
1764 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.angular_size > b.angular_size; });
1769CollisionDetection::Gap CollisionDetection::floodFillGap(
const AngularBins &bins,
int start_theta,
int start_phi, std::vector<std::vector<bool>> &visited,
const Cone &cone) {
1771 std::queue<std::pair<int, int>> queue;
1772 queue.push({start_theta, start_phi});
1774 float total_solid_angle = 0;
1775 vec3 weighted_center(0, 0, 0);
1777 while (!queue.empty()) {
1778 auto [theta, phi] = queue.front();
1781 if (visited[theta][phi] || bins.isCovered(theta, phi))
1783 visited[theta][phi] =
true;
1786 float bin_solid_angle = calculateBinSolidAngle(theta, phi, bins, cone.half_angle);
1787 total_solid_angle += bin_solid_angle;
1790 vec3 bin_direction = binIndicesToCartesian(theta, phi, bins, cone);
1791 weighted_center = weighted_center + bin_direction * bin_solid_angle;
1794 addUnoccupiedNeighbors(theta, phi, bins, visited, queue);
1797 gap.angular_size = total_solid_angle;
1798 gap.center_direction = weighted_center.magnitude() > 1e-6f ? weighted_center.normalize() : cone.axis;
1803float CollisionDetection::calculateBinSolidAngle(
int theta_bin,
int phi_bin,
const AngularBins &bins,
float cone_half_angle) {
1805 float theta_step = 2.0f *
M_PI / bins.theta_divisions;
1806 float phi_step = cone_half_angle / bins.phi_divisions;
1809 float phi = (phi_bin + 0.5f) * phi_step;
1810 return theta_step * phi_step * sinf(phi);
1813vec3 CollisionDetection::binIndicesToCartesian(
int theta_bin,
int phi_bin,
const AngularBins &bins,
const Cone &cone) {
1815 float theta = (theta_bin + 0.5f) * 2.0f * M_PI / bins.theta_divisions;
1816 float phi = (phi_bin + 0.5f) * cone.half_angle / bins.phi_divisions;
1825 float sin_phi = sinf(phi);
1826 float cos_phi = cosf(phi);
1827 float sin_theta = sinf(theta);
1828 float cos_theta = cosf(theta);
1830 vec3 direction = cone.axis * cos_phi + (right * cos_theta + forward * sin_theta) * sin_phi;
1834void CollisionDetection::addUnoccupiedNeighbors(
int theta,
int phi,
const AngularBins &bins, std::vector<std::vector<bool>> &visited, std::queue<std::pair<int, int>> &queue) {
1836 const int neighbors[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
1838 for (
int i = 0; i < 4; i++) {
1839 int new_theta = theta + neighbors[i][0];
1840 int new_phi = phi + neighbors[i][1];
1844 new_theta += bins.theta_divisions;
1845 if (new_theta >= bins.theta_divisions)
1846 new_theta -= bins.theta_divisions;
1849 if (new_phi >= 0 && new_phi < bins.phi_divisions) {
1850 if (!visited[new_theta][new_phi] && !bins.isCovered(new_theta, new_phi)) {
1851 queue.push({new_theta, new_phi});
1868 if (initialSamples <= 0 || half_angle <= 0.0f || half_angle >
M_PI) {
1869 if (printmessages) {
1870 std::cerr <<
"WARNING: Invalid parameters for findOptimalConePath" << std::endl;
1875 if (bvh_nodes.empty()) {
1882 std::vector<Gap> detected_gaps = detectGapsInCone(apex, centralAxis, half_angle, height, initialSamples);
1884 if (detected_gaps.empty()) {
1894 scoreGapsByFishEyeMetric(detected_gaps, centralAxis);
1897 result.
direction = findOptimalGapDirection(detected_gaps, centralAxis);
1900 float max_distance = (height > 0.0f) ? height : -1.0f;
1905 if (!detected_gaps.empty()) {
1907 const Gap &best_gap = detected_gaps[0];
1908 result.
confidence = std::min(1.0f, best_gap.angular_size * 10.0f);
1914#ifdef HELIOS_CUDA_AVAILABLE
1915void CollisionDetection::allocateGPUMemory() {
1916 if (gpu_memory_allocated) {
1920 if (bvh_nodes.empty() || primitive_indices.empty()) {
1925 d_bvh_nodes =
nullptr;
1926 d_primitive_indices =
nullptr;
1931 if (printmessages) {
1932 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): No usable GPU available. Falling back to CPU-only mode." << std::endl;
1934 gpu_acceleration_enabled =
false;
1939 size_t bvh_size = bvh_nodes.size() *
sizeof(
GPUBVHNode);
1940 size_t indices_size = primitive_indices.size() *
sizeof(
uint);
1943 if (bvh_size == 0 || indices_size == 0) {
1948 cudaError_t err = cudaMalloc(&d_bvh_nodes, bvh_size);
1949 if (err != cudaSuccess) {
1951 if (printmessages) {
1952 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): Failed to allocate GPU memory (" << cudaGetErrorString(err) <<
"). Falling back to CPU-only mode." << std::endl;
1954 gpu_acceleration_enabled =
false;
1959 err = cudaMalloc((
void **) &d_primitive_indices, indices_size);
1960 if (err != cudaSuccess) {
1962 cudaFree(d_bvh_nodes);
1963 d_bvh_nodes =
nullptr;
1964 if (printmessages) {
1965 std::cout <<
"WARNING (CollisionDetection::allocateGPUMemory): Failed to allocate primitive indices (" << cudaGetErrorString(err) <<
"). Falling back to CPU-only mode." << std::endl;
1967 gpu_acceleration_enabled =
false;
1972 gpu_memory_allocated =
true;
1976#ifdef HELIOS_CUDA_AVAILABLE
1977void CollisionDetection::freeGPUMemory() {
1978 if (!gpu_memory_allocated)
1982 cudaFree(d_bvh_nodes);
1983 d_bvh_nodes =
nullptr;
1986 if (d_primitive_indices) {
1987 cudaFree(d_primitive_indices);
1988 d_primitive_indices =
nullptr;
1991 if (d_primitive_types) {
1992 cudaFree(d_primitive_types);
1993 d_primitive_types =
nullptr;
1996 if (d_primitive_vertices) {
1997 cudaFree(d_primitive_vertices);
1998 d_primitive_vertices =
nullptr;
2001 if (d_vertex_offsets) {
2002 cudaFree(d_vertex_offsets);
2003 d_vertex_offsets =
nullptr;
2007 cudaFree(d_mask_data);
2008 d_mask_data =
nullptr;
2010 if (d_mask_offsets) {
2011 cudaFree(d_mask_offsets);
2012 d_mask_offsets =
nullptr;
2015 cudaFree(d_mask_sizes);
2016 d_mask_sizes =
nullptr;
2019 cudaFree(d_mask_IDs);
2020 d_mask_IDs =
nullptr;
2023 cudaFree(d_uv_data);
2024 d_uv_data =
nullptr;
2030 d_gpu_has_masks =
false;
2032 d_gpu_node_count = 0;
2033 d_gpu_primitive_count = 0;
2034 d_gpu_total_vertex_count = 0;
2036 gpu_memory_allocated =
false;
2040#ifdef HELIOS_CUDA_AVAILABLE
2041void CollisionDetection::transferBVHToGPU() {
2042 if (!gpu_acceleration_enabled || bvh_nodes.empty()) {
2047 if (gpu_memory_allocated) {
2050 allocateGPUMemory();
2054 if (!gpu_acceleration_enabled) {
2059 if (!gpu_memory_allocated || d_bvh_nodes ==
nullptr || d_primitive_indices ==
nullptr) {
2064 std::vector<GPUBVHNode> gpu_nodes(bvh_nodes.size());
2065 for (
size_t i = 0; i < bvh_nodes.size(); i++) {
2066 const BVHNode &cpu_node = bvh_nodes[i];
2075 gpu_node.
is_leaf = cpu_node.is_leaf ? 1 : 0;
2080 cudaError_t err = cudaMemcpy(d_bvh_nodes, gpu_nodes.data(), gpu_nodes.size() *
sizeof(
GPUBVHNode), cudaMemcpyHostToDevice);
2081 if (err != cudaSuccess) {
2082 helios_runtime_error(
"CUDA error transferring BVH nodes: " + std::string(cudaGetErrorString(err)));
2085 err = cudaMemcpy(d_primitive_indices, primitive_indices.data(), primitive_indices.size() *
sizeof(
uint), cudaMemcpyHostToDevice);
2086 if (err != cudaSuccess) {
2087 helios_runtime_error(
"CUDA error transferring primitive indices: " + std::string(cudaGetErrorString(err)));
2093 std::vector<int> primitive_types;
2094 std::vector<float> primitive_vertices_xyz;
2095 std::vector<unsigned int> vertex_offsets;
2096 std::vector<unsigned char> mask_data;
2097 std::vector<unsigned int> mask_offsets;
2098 std::vector<int> mask_sizes;
2099 std::vector<int> mask_IDs;
2100 std::vector<float> uv_data;
2101 std::vector<int> uv_IDs;
2102 buildGPUGeometrySoA(primitive_types, primitive_vertices_xyz, vertex_offsets, mask_data, mask_offsets, mask_sizes, mask_IDs, uv_data, uv_IDs);
2104 d_gpu_node_count =
static_cast<int>(bvh_nodes.size());
2105 d_gpu_primitive_count =
static_cast<int>(primitive_indices.size());
2106 d_gpu_total_vertex_count =
static_cast<int>(primitive_vertices_xyz.size() / 3);
2108 const size_t types_size = primitive_types.size() *
sizeof(int);
2109 const size_t vertices_size = primitive_vertices_xyz.size() *
sizeof(float);
2110 const size_t offsets_size = vertex_offsets.size() *
sizeof(
unsigned int);
2112 if ((err = cudaMalloc(&d_primitive_types, types_size)) != cudaSuccess) {
2113 helios_runtime_error(
"CUDA error allocating GPU primitive types: " + std::string(cudaGetErrorString(err)));
2115 if ((err = cudaMalloc(&d_primitive_vertices, vertices_size)) != cudaSuccess) {
2116 helios_runtime_error(
"CUDA error allocating GPU primitive vertices: " + std::string(cudaGetErrorString(err)));
2118 if ((err = cudaMalloc(&d_vertex_offsets, offsets_size)) != cudaSuccess) {
2119 helios_runtime_error(
"CUDA error allocating GPU vertex offsets: " + std::string(cudaGetErrorString(err)));
2122 if ((err = cudaMemcpy(d_primitive_types, primitive_types.data(), types_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2123 helios_runtime_error(
"CUDA error transferring primitive types: " + std::string(cudaGetErrorString(err)));
2125 if ((err = cudaMemcpy(d_primitive_vertices, primitive_vertices_xyz.data(), vertices_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2126 helios_runtime_error(
"CUDA error transferring primitive vertices: " + std::string(cudaGetErrorString(err)));
2128 if ((err = cudaMemcpy(d_vertex_offsets, vertex_offsets.data(), offsets_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2129 helios_runtime_error(
"CUDA error transferring vertex offsets: " + std::string(cudaGetErrorString(err)));
2135 d_gpu_has_masks = !mask_offsets.empty();
2137 const size_t mask_IDs_size = mask_IDs.size() *
sizeof(int);
2138 const size_t uv_data_size = uv_data.size() *
sizeof(float);
2139 const size_t uv_IDs_size = uv_IDs.size() *
sizeof(int);
2140 if ((err = cudaMalloc(&d_mask_IDs, mask_IDs_size)) != cudaSuccess) {
2141 helios_runtime_error(
"CUDA error allocating GPU mask IDs: " + std::string(cudaGetErrorString(err)));
2143 if ((err = cudaMalloc(&d_uv_data, uv_data_size)) != cudaSuccess) {
2144 helios_runtime_error(
"CUDA error allocating GPU UV data: " + std::string(cudaGetErrorString(err)));
2146 if ((err = cudaMalloc(&d_uv_IDs, uv_IDs_size)) != cudaSuccess) {
2147 helios_runtime_error(
"CUDA error allocating GPU UV IDs: " + std::string(cudaGetErrorString(err)));
2149 if ((err = cudaMemcpy(d_mask_IDs, mask_IDs.data(), mask_IDs_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2150 helios_runtime_error(
"CUDA error transferring mask IDs: " + std::string(cudaGetErrorString(err)));
2152 if ((err = cudaMemcpy(d_uv_data, uv_data.data(), uv_data_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2153 helios_runtime_error(
"CUDA error transferring UV data: " + std::string(cudaGetErrorString(err)));
2155 if ((err = cudaMemcpy(d_uv_IDs, uv_IDs.data(), uv_IDs_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2156 helios_runtime_error(
"CUDA error transferring UV IDs: " + std::string(cudaGetErrorString(err)));
2159 if (d_gpu_has_masks) {
2160 const size_t mask_data_size = mask_data.size() *
sizeof(
unsigned char);
2161 const size_t mask_offsets_size = mask_offsets.size() *
sizeof(
unsigned int);
2162 const size_t mask_sizes_size = mask_sizes.size() *
sizeof(int);
2163 if ((err = cudaMalloc(&d_mask_data, mask_data_size)) != cudaSuccess) {
2164 helios_runtime_error(
"CUDA error allocating GPU mask data: " + std::string(cudaGetErrorString(err)));
2166 if ((err = cudaMalloc(&d_mask_offsets, mask_offsets_size)) != cudaSuccess) {
2167 helios_runtime_error(
"CUDA error allocating GPU mask offsets: " + std::string(cudaGetErrorString(err)));
2169 if ((err = cudaMalloc(&d_mask_sizes, mask_sizes_size)) != cudaSuccess) {
2170 helios_runtime_error(
"CUDA error allocating GPU mask sizes: " + std::string(cudaGetErrorString(err)));
2172 if ((err = cudaMemcpy(d_mask_data, mask_data.data(), mask_data_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2173 helios_runtime_error(
"CUDA error transferring mask data: " + std::string(cudaGetErrorString(err)));
2175 if ((err = cudaMemcpy(d_mask_offsets, mask_offsets.data(), mask_offsets_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2176 helios_runtime_error(
"CUDA error transferring mask offsets: " + std::string(cudaGetErrorString(err)));
2178 if ((err = cudaMemcpy(d_mask_sizes, mask_sizes.data(), mask_sizes_size, cudaMemcpyHostToDevice)) != cudaSuccess) {
2179 helios_runtime_error(
"CUDA error transferring mask sizes: " + std::string(cudaGetErrorString(err)));
2184void CollisionDetection::buildGPUGeometrySoA(std::vector<int> &primitive_types, std::vector<float> &primitive_vertices_xyz, std::vector<unsigned int> &vertex_offsets, std::vector<unsigned char> &mask_data, std::vector<unsigned int> &mask_offsets,
2185 std::vector<int> &mask_sizes, std::vector<int> &mask_IDs, std::vector<float> &uv_data, std::vector<int> &uv_IDs) {
2189 const size_t nprim = primitive_indices.size();
2190 primitive_types.assign(nprim, 0);
2191 vertex_offsets.assign(nprim, 0);
2192 primitive_vertices_xyz.clear();
2193 primitive_vertices_xyz.reserve(nprim * 4 * 3);
2200 mask_offsets.clear();
2202 mask_IDs.assign(nprim, -1);
2203 uv_data.assign(nprim * 4 * 2, 0.f);
2204 uv_IDs.assign(nprim, -1);
2205 std::map<std::string, int> texture_to_mask_idx;
2208 primitive_vertex_warnings.
setEnabled(printmessages);
2211 primitive_vertices_xyz.push_back(v.x);
2212 primitive_vertices_xyz.push_back(v.y);
2213 primitive_vertices_xyz.push_back(v.z);
2215 auto push_zero = [&]() { push_vertex(
make_vec3(0, 0, 0)); };
2217 unsigned int vertex_index = 0;
2218 for (
size_t i = 0; i < nprim; i++) {
2219 vertex_offsets[i] = vertex_index;
2221 const uint UUID = primitive_indices[i];
2223 primitive_types[i] =
static_cast<int>(ptype);
2226 if ((ptype == PRIMITIVE_TYPE_PATCH || ptype == PRIMITIVE_TYPE_TRIANGLE) &&
context->primitiveTextureHasTransparencyChannel(UUID)) {
2227 const std::string texfile =
context->getPrimitiveTextureFile(UUID);
2228 auto cached = texture_to_mask_idx.find(texfile);
2230 if (cached != texture_to_mask_idx.end()) {
2231 mask_idx = cached->second;
2233 const std::vector<std::vector<bool>> *trans =
context->getPrimitiveTextureTransparencyData(UUID);
2235 mask_idx =
static_cast<int>(mask_offsets.size());
2236 mask_offsets.push_back(
static_cast<unsigned int>(mask_data.size()));
2237 mask_sizes.push_back(tex_size.
x);
2238 mask_sizes.push_back(tex_size.
y);
2240 for (
int y = 0; y < tex_size.
y; y++) {
2241 for (
int x = 0; x < tex_size.
x; x++) {
2242 mask_data.push_back((trans !=
nullptr && y <
static_cast<int>(trans->size()) && x < static_cast<int>((*trans)[y].size()) && (*trans)[y][x]) ? 1u : 0u);
2245 texture_to_mask_idx[texfile] = mask_idx;
2247 mask_IDs[i] = mask_idx;
2251 std::vector<vec2> uv =
context->getPrimitiveTextureUV(UUID);
2253 if (uv.size() >= need) {
2254 for (
size_t v = 0; v < need; v++) {
2255 uv_data[i * 8 + v * 2 + 0] = uv[v].x;
2256 uv_data[i * 8 + v * 2 + 1] = uv[v].y;
2262 std::vector<vec3> vertices =
context->getPrimitiveVertices(UUID);
2264 if (ptype == PRIMITIVE_TYPE_TRIANGLE) {
2265 if (vertices.size() >= 3) {
2266 for (
int v = 0; v < 3; v++) {
2267 push_vertex(vertices[v]);
2271 primitive_vertex_warnings.
addWarning(
"triangle_wrong_vertex_count",
"Triangle primitive " + std::to_string(primitive_indices[i]) +
" has " + std::to_string(vertices.size()) +
" vertices");
2272 for (
int v = 0; v < 4; v++) {
2276 }
else if (ptype == PRIMITIVE_TYPE_PATCH) {
2277 if (vertices.size() >= 4) {
2278 for (
int v = 0; v < 4; v++) {
2279 push_vertex(vertices[v]);
2282 primitive_vertex_warnings.
addWarning(
"patch_wrong_vertex_count",
"Patch primitive " + std::to_string(primitive_indices[i]) +
" has " + std::to_string(vertices.size()) +
" vertices");
2283 for (
int v = 0; v < 4; v++) {
2287 }
else if (ptype == PRIMITIVE_TYPE_VOXEL) {
2288 vec3 voxel_min = vertices.empty() ?
make_vec3(0, 0, 0) : vertices[0];
2289 vec3 voxel_max = voxel_min;
2290 for (
const auto &vertex: vertices) {
2291 voxel_min.
x = std::min(voxel_min.
x, vertex.x);
2292 voxel_min.
y = std::min(voxel_min.
y, vertex.y);
2293 voxel_min.
z = std::min(voxel_min.
z, vertex.z);
2294 voxel_max.
x = std::max(voxel_max.
x, vertex.x);
2295 voxel_max.
y = std::max(voxel_max.
y, vertex.y);
2296 voxel_max.
z = std::max(voxel_max.
z, vertex.z);
2298 push_vertex(voxel_min);
2299 push_vertex(voxel_max);
2303 for (
int v = 0; v < 4; v++) {
2310 primitive_vertex_warnings.
report(std::cerr);
2314void CollisionDetection::markBVHDirty() {
2316 last_processed_uuids.clear();
2317 last_processed_deleted_uuids.clear();
2318 last_bvh_geometry.clear();
2325#ifdef HELIOS_CUDA_AVAILABLE
2330void CollisionDetection::incrementalUpdateBVH(
const std::set<uint> &added_geometry,
const std::set<uint> &removed_geometry,
const std::set<uint> &final_geometry) {
2333 for (
uint uuid: added_geometry) {
2334 if (!
context->doesPrimitiveExist(uuid)) {
2335 if (printmessages) {
2336 std::cerr <<
"Warning: Added primitive " << uuid <<
" does not exist, falling back to full rebuild" << std::endl;
2338 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
2348 for (
uint uuid: added_geometry) {
2349 updatePrimitiveAABBCache(uuid);
2353 for (
uint uuid: removed_geometry) {
2354 primitive_aabbs_cache.erase(uuid);
2361 size_t total_changes = added_geometry.size() + removed_geometry.size();
2362 size_t current_size = final_geometry.size();
2365 if (current_size > 0 && !bvh_nodes.empty() && (
float(total_changes) /
float(current_size)) < 0.05f) {
2367 bool insertion_successful =
true;
2370 if (!removed_geometry.empty()) {
2371 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());
2375 for (
uint uuid: added_geometry) {
2376 primitive_indices.push_back(uuid);
2381 if (insertion_successful && total_changes < 50) {
2382 if (printmessages) {
2383 std::cout <<
"Using targeted tree update for " << total_changes <<
" changes" << std::endl;
2389 optimizedRebuildBVH(final_geometry);
2395 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
2399 last_bvh_geometry = final_geometry;
2404bool CollisionDetection::validateUUIDs(
const std::vector<uint> &UUIDs)
const {
2408 bool all_valid =
true;
2409 for (
uint UUID: UUIDs) {
2410 if (!
context->doesPrimitiveExist(UUID)) {
2411 warnings.
addWarning(
"primitive_uuid_not_exist",
"Primitive UUID " + std::to_string(UUID) +
" does not exist - skipping");
2416 warnings.
report(std::cerr);
2420bool CollisionDetection::rayPrimitiveIntersection(
const vec3 &origin,
const vec3 &direction,
uint primitive_UUID,
float &distance)
const {
2422 if (!
context->doesPrimitiveExist(primitive_UUID)) {
2429 std::vector<vec3> vertices =
context->getPrimitiveVertices(primitive_UUID);
2431 if (vertices.empty()) {
2437 float min_distance = std::numeric_limits<float>::max();
2439 if (type == PRIMITIVE_TYPE_TRIANGLE) {
2441 if (vertices.size() >= 3) {
2442 const vec3 &v0 = vertices[0];
2443 const vec3 &v1 = vertices[1];
2444 const vec3 &v2 = vertices[2];
2447 float a = v0.
x - v1.
x, b = v0.
x - v2.
x, c = direction.
x, d = v0.
x - origin.
x;
2448 float e = v0.
y - v1.
y, f = v0.
y - v2.
y, g = direction.
y, h = v0.
y - origin.
y;
2449 float i = v0.
z - v1.
z, j = v0.
z - v2.
z, k = direction.
z, l = v0.
z - origin.
z;
2451 float m = f * k - g * j, n = h * k - g * l, p = f * l - h * j;
2452 float q = g * i - e * k, s = e * j - f * i;
2454 float denom = a * m + b * q + c * s;
2455 if (std::abs(denom) < 1e-8f) {
2459 float inv_denom = 1.0f / denom;
2461 float e1 = d * m - b * n - c * p;
2462 float beta = e1 * inv_denom;
2465 float r = e * l - h * i;
2466 float e2 = a * n + d * q + c * r;
2467 float gamma = e2 * inv_denom;
2469 if (gamma >= 0.0f && beta + gamma <= 1.0f) {
2470 float e3 = a * p - b * r + d * s;
2471 float t = e3 * inv_denom;
2473 if (t > 1e-8f && t < min_distance) {
2480 }
else if (type == PRIMITIVE_TYPE_PATCH) {
2482 if (vertices.size() >= 4) {
2483 const vec3 &v0 = vertices[0];
2484 const vec3 &v1 = vertices[1];
2485 const vec3 &v2 = vertices[2];
2486 const vec3 &v3 = vertices[3];
2497 float denom = direction * normal;
2498 if (std::abs(denom) > 1e-8f) {
2499 float t = (anchor - origin) * normal / denom;
2501 if (t > 1e-8f && t < 1e8f) {
2503 vec3 p = origin + direction * t;
2504 vec3 d = p - anchor;
2507 float ddota = d * a;
2508 float ddotb = d * b;
2511 if (ddota >= 0.0f && ddota <= (a * a) && ddotb >= 0.0f && ddotb <= (b * b)) {
2513 if (t < min_distance) {
2521 }
else if (type == PRIMITIVE_TYPE_VOXEL) {
2523 if (vertices.size() == 8) {
2525 vec3 aabb_min = vertices[0];
2526 vec3 aabb_max = vertices[0];
2528 for (
int i = 1; i < 8; i++) {
2529 aabb_min.
x = std::min(aabb_min.
x, vertices[i].x);
2530 aabb_min.
y = std::min(aabb_min.
y, vertices[i].y);
2531 aabb_min.
z = std::min(aabb_min.
z, vertices[i].z);
2532 aabb_max.
x = std::max(aabb_max.
x, vertices[i].x);
2533 aabb_max.
y = std::max(aabb_max.
y, vertices[i].y);
2534 aabb_max.
z = std::max(aabb_max.
z, vertices[i].z);
2538 float t_near = -std::numeric_limits<float>::max();
2539 float t_far = std::numeric_limits<float>::max();
2542 for (
int i = 0; i < 3; i++) {
2543 float ray_dir_component = (i == 0) ? direction.
x : (i == 1) ? direction.y : direction.z;
2544 float ray_orig_component = (i == 0) ? origin.
x : (i == 1) ? origin.y : origin.z;
2545 float aabb_min_component = (i == 0) ? aabb_min.
x : (i == 1) ? aabb_min.y : aabb_min.z;
2546 float aabb_max_component = (i == 0) ? aabb_max.
x : (i == 1) ? aabb_max.y : aabb_max.z;
2548 if (std::abs(ray_dir_component) < 1e-8f) {
2550 if (ray_orig_component < aabb_min_component || ray_orig_component > aabb_max_component) {
2555 float t1 = (aabb_min_component - ray_orig_component) / ray_dir_component;
2556 float t2 = (aabb_max_component - ray_orig_component) / ray_dir_component;
2564 t_near = std::max(t_near, t1);
2565 t_far = std::min(t_far, t2);
2568 if (t_near > t_far) {
2575 if (t_far >= 0.0f && t_near < min_distance) {
2577 float intersection_distance = (t_near >= 1e-8f) ? t_near : t_far;
2578 if (intersection_distance >= 1e-8f) {
2579 min_distance = intersection_distance;
2587 distance = min_distance;
2592 }
catch (
const std::exception &e) {
2600 std::vector<uint> uuids_to_process = UUIDs.empty() ?
context->getAllUUIDs() : UUIDs;
2603 std::vector<uint> planar_primitives;
2604 for (
uint uuid: uuids_to_process) {
2606 planar_primitives.push_back(uuid);
2619 if (i < 0 || i >=
static_cast<int>(grid_cells.size()) || j < 0 || j >=
static_cast<int>(grid_cells[i].size()) || k < 0 || k >=
static_cast<int>(grid_cells[i][j].size())) {
2620 helios_runtime_error(
"ERROR (CollisionDetection::getGridIntersections): Grid indices out of bounds");
2622 return grid_cells[i][j][k];
2626 if (printmessages) {
2627 std::cerr <<
"WARNING: optimizeLayout not yet implemented" << std::endl;
2632int CollisionDetection::countRayIntersections(
const vec3 &origin,
const vec3 &direction,
float max_distance) {
2634 int intersection_count = 0;
2636 if (bvh_nodes.empty()) {
2637 return intersection_count;
2642 float min_distance = 0.05f;
2648 std::vector<uint> node_stack;
2649 node_stack.push_back(0);
2651 while (!node_stack.empty()) {
2652 uint node_idx = node_stack.back();
2653 node_stack.pop_back();
2655 if (node_idx >= bvh_nodes.size())
2658 const BVHNode &node = bvh_nodes[node_idx];
2662 if (!rayAABBIntersect(origin, direction, node.
aabb_min, node.
aabb_max, t_min, t_max)) {
2667 if (t_max < min_distance) {
2670 if (max_distance > 0.0f && t_min > max_distance) {
2676 for (
uint i = 0; i < node.primitive_count; i++) {
2677 uint primitive_id = primitive_indices[node.primitive_start + i];
2680 if (!
context->doesPrimitiveExist(primitive_id)) {
2683 vec3 prim_min, prim_max;
2684 context->getPrimitiveBoundingBox(primitive_id, prim_min, prim_max);
2687 float prim_t_min, prim_t_max;
2688 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2690 bool within_min_distance = prim_t_min >= min_distance;
2691 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2693 if (within_min_distance && within_max_distance) {
2694 intersection_count++;
2709 return intersection_count;
2712bool CollisionDetection::findNearestRayIntersection(
const vec3 &origin,
const vec3 &direction,
const std::set<uint> &candidate_UUIDs,
float &nearest_distance,
float max_distance) {
2714 nearest_distance = std::numeric_limits<float>::max();
2715 bool found_intersection =
false;
2718 bool check_static_bvh = hierarchical_bvh_enabled && static_bvh_valid && !static_bvh_nodes.empty();
2719 bool check_dynamic_bvh = !bvh_nodes.empty();
2721 if (!check_static_bvh && !check_dynamic_bvh) {
2730 auto traverseBVH = [&](
const std::vector<BVHNode> &nodes,
const std::vector<uint> &primitives,
const char *bvh_name) {
2735 std::vector<uint> node_stack;
2736 node_stack.push_back(0);
2738 while (!node_stack.empty()) {
2739 uint node_idx = node_stack.back();
2740 node_stack.pop_back();
2742 if (node_idx >= nodes.size()) {
2746 const BVHNode &node = nodes[node_idx];
2750 if (!rayAABBIntersect(origin, direction, node.
aabb_min, node.
aabb_max, t_min, t_max)) {
2755 if (max_distance > 0.0f && t_min > max_distance) {
2760 if (t_min > nearest_distance) {
2766 for (
uint i = 0; i < node.primitive_count; i++) {
2767 uint primitive_id = primitives[node.primitive_start + i];
2771 if (!candidate_UUIDs.empty() && candidate_UUIDs.find(primitive_id) == candidate_UUIDs.end()) {
2777 if (!
context->doesPrimitiveExist(primitive_id)) {
2781 vec3 prim_min, prim_max;
2782 context->getPrimitiveBoundingBox(primitive_id, prim_min, prim_max);
2785 float prim_t_min, prim_t_max;
2786 if (rayAABBIntersect(origin, direction, prim_min, prim_max, prim_t_min, prim_t_max)) {
2788 bool within_max_distance = (max_distance <= 0.0f) || (prim_t_min <= max_distance);
2790 if (within_max_distance && prim_t_min > 0.0f && prim_t_min < nearest_distance) {
2793 nearest_distance = prim_t_min;
2794 found_intersection =
true;
2811 if (check_static_bvh) {
2812 traverseBVH(static_bvh_nodes, static_bvh_primitives,
"static");
2816 if (check_dynamic_bvh) {
2817 traverseBVH(bvh_nodes, primitive_indices,
"dynamic");
2820 return found_intersection;
2828 if (candidate_UUIDs.empty()) {
2829 warnings.
addWarning(
"no_candidate_uuids",
"No candidate UUIDs provided");
2830 warnings.
report(std::cerr);
2835 float dir_magnitude = direction.
magnitude();
2836 if (std::abs(dir_magnitude - 1.0f) > 1e-6f) {
2837 warnings.
addWarning(
"direction_not_normalized",
"Direction vector is not normalized (magnitude = " + std::to_string(dir_magnitude) +
")");
2838 warnings.
report(std::cerr);
2844 std::vector<uint> valid_candidates;
2845 for (
uint uuid: candidate_UUIDs) {
2846 if (
context->doesPrimitiveExist(uuid)) {
2847 valid_candidates.push_back(uuid);
2849 warnings.
addWarning(
"invalid_candidate_uuid",
"Skipping invalid UUID " + std::to_string(uuid));
2854 if (valid_candidates.empty()) {
2855 warnings.
addWarning(
"no_valid_candidates",
"No valid candidate UUIDs after filtering");
2856 warnings.
report(std::cerr);
2860 float nearest_distance_found = std::numeric_limits<float>::max();
2861 vec3 nearest_obstacle_direction;
2862 bool found_forward_surface =
false;
2865 for (
uint primitive_id: valid_candidates) {
2867 vec3 surface_normal =
context->getPrimitiveNormal(primitive_id);
2868 std::vector<vec3> vertices =
context->getPrimitiveVertices(primitive_id);
2870 if (vertices.empty()) {
2875 vec3 point_on_plane = vertices[0];
2878 vec3 to_origin = origin - point_on_plane;
2879 float distance_to_plane = to_origin * surface_normal;
2882 float surface_distance = std::abs(distance_to_plane);
2885 vec3 surface_direction;
2886 if (distance_to_plane > 0) {
2888 surface_direction = -surface_normal;
2891 surface_direction = surface_normal;
2895 float dot_product = surface_direction * direction;
2897 if (dot_product > 0.0f) {
2898 if (surface_distance < nearest_distance_found) {
2899 nearest_distance_found = surface_distance;
2900 nearest_obstacle_direction = surface_direction;
2901 found_forward_surface =
true;
2906 if (found_forward_surface) {
2907 distance = nearest_distance_found;
2908 obstacle_direction = nearest_obstacle_direction;
2909 warnings.
report(std::cerr);
2913 warnings.
report(std::cerr);
2923 std::vector<uint> effective_candidates;
2924 if (tree_based_bvh_enabled) {
2926 float spatial_filter_distance = height * 1.25f;
2930 effective_candidates = candidate_UUIDs;
2933 if (effective_candidates.empty()) {
2938 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
2939 warnings.
addWarning(
"invalid_half_angle",
"Invalid half_angle " + std::to_string(half_angle));
2940 warnings.
report(std::cerr);
2944 if (height <= 0.0f) {
2945 warnings.
addWarning(
"invalid_height",
"Invalid height " + std::to_string(height));
2946 warnings.
report(std::cerr);
2954 if (bvh_nodes.empty()) {
2959 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
2962 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
2964 float nearest_distance = std::numeric_limits<float>::max();
2965 vec3 nearest_direction;
2966 bool found_obstacle =
false;
2969 std::vector<RayQuery> ray_queries;
2970 ray_queries.reserve(ray_directions.size());
2972 for (
const vec3 &ray_dir: ray_directions) {
2973 ray_queries.emplace_back(apex, ray_dir, height, candidate_UUIDs);
2978 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
2981 for (
size_t i = 0; i < hit_results.size(); ++i) {
2982 const HitResult &result = hit_results[i];
2984 if (result.
hit && result.
distance < nearest_distance) {
2985 nearest_distance = result.
distance;
2986 nearest_direction = ray_directions[i];
2987 found_obstacle =
true;
2991 if (found_obstacle) {
2992 distance = nearest_distance;
2993 obstacle_direction = nearest_direction;
2994 warnings.
report(std::cerr);
2998 warnings.
report(std::cerr);
3003 vec3 &obstacle_direction,
int num_rays) {
3009 std::vector<uint> effective_candidates;
3010 if (tree_based_bvh_enabled) {
3014 if (printmessages && !effective_candidates.empty()) {
3015 std::cout <<
"Per-tree findNearestSolidObstacleInCone: Using " << effective_candidates.size() <<
" relevant targets instead of " << candidate_UUIDs.size() <<
" total targets" << std::endl;
3018 effective_candidates = candidate_UUIDs;
3021 if (effective_candidates.empty()) {
3026 if (half_angle <= 0.0f || half_angle >
M_PI / 2.0f) {
3027 warnings.
addWarning(
"invalid_half_angle",
"Invalid half_angle " + std::to_string(half_angle));
3028 warnings.
report(std::cerr);
3032 if (height <= 0.0f) {
3033 warnings.
addWarning(
"invalid_height",
"Invalid height " + std::to_string(height));
3034 warnings.
report(std::cerr);
3042 if (bvh_nodes.empty()) {
3047 std::set<uint> candidate_set(effective_candidates.begin(), effective_candidates.end());
3050 std::vector<vec3> ray_directions = sampleDirectionsInCone(apex, axis, half_angle, num_rays);
3052 float nearest_distance = std::numeric_limits<float>::max();
3053 vec3 nearest_direction;
3054 bool found_obstacle =
false;
3057 std::vector<RayQuery> ray_queries;
3058 ray_queries.reserve(ray_directions.size());
3060 for (
const vec3 &ray_dir: ray_directions) {
3061 ray_queries.emplace_back(apex, ray_dir, height, effective_candidates);
3066 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
3069 for (
size_t i = 0; i < hit_results.size(); ++i) {
3070 const HitResult &result = hit_results[i];
3072 if (result.
hit && result.
distance < nearest_distance) {
3073 nearest_distance = result.
distance;
3074 nearest_direction = ray_directions[i];
3075 found_obstacle =
true;
3079 if (found_obstacle) {
3080 distance = nearest_distance;
3081 obstacle_direction = nearest_direction;
3082 warnings.
report(std::cerr);
3086 warnings.
report(std::cerr);
3091std::vector<helios::vec3> CollisionDetection::sampleDirectionsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
int num_samples) {
3093 std::vector<vec3> directions;
3094 directions.reserve(num_samples);
3096 if (num_samples <= 0 || half_angle <= 0.0f) {
3101 vec3 axis = central_axis;
3106 if (std::abs(axis.
z) < 0.9f) {
3116 std::random_device rd;
3117 std::mt19937 gen(rd());
3118 std::uniform_real_distribution<float> uniform_dist(0.0f, 1.0f);
3120 int samples_generated = 0;
3121 int max_attempts = num_samples * 10;
3124 while (samples_generated < num_samples && attempts < max_attempts) {
3128 float u1 = uniform_dist(gen);
3129 float u2 = uniform_dist(gen);
3132 if (samples_generated > 0) {
3133 float stratum_u1 = (float) samples_generated / (
float) num_samples;
3134 float stratum_u2 = uniform_dist(gen);
3135 u1 = (stratum_u1 + u1 / (float) num_samples);
3143 float cos_half_angle = cosf(half_angle);
3144 float cos_theta = cos_half_angle + u1 * (1.0f - cos_half_angle);
3145 float sin_theta = sqrtf(1.0f - cos_theta * cos_theta);
3146 float phi = 2.0f *
M_PI * u2;
3149 float x = sin_theta * cosf(phi);
3150 float y = sin_theta * sinf(phi);
3151 float z = cos_theta;
3155 vec3 world_direction = u * local_direction.
x + v * local_direction.
y + axis * local_direction.
z;
3159 float dot_product = world_direction * axis;
3160 if (dot_product >= cos_half_angle - 1e-6f) {
3161 directions.push_back(world_direction);
3162 samples_generated++;
3167 while (directions.size() < (
size_t) num_samples) {
3168 directions.push_back(axis);
3177std::vector<uint> CollisionDetection::getCandidatesUsingSpatialGrid(
const Cone &cone,
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
3179 vec3 cone_base = apex + central_axis * height;
3180 float cone_base_radius = height * tan(half_angle);
3182 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));
3183 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));
3186 std::vector<uint> all_primitives;
3187 if (!primitive_indices.empty()) {
3188 all_primitives = primitive_indices;
3191 all_primitives =
context->getAllUUIDs();
3195 return filterPrimitivesParallel(cone, all_primitives);
3200std::vector<uint> CollisionDetection::getCandidatePrimitivesInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height) {
3201 std::vector<uint> candidates;
3204 Cone cone{apex, central_axis, half_angle, height};
3207 candidates = getCandidatesUsingSpatialGrid(cone, apex, central_axis, half_angle, height);
3215std::vector<CollisionDetection::Gap> CollisionDetection::detectGapsInCone(
const vec3 &apex,
const vec3 ¢ral_axis,
float half_angle,
float height,
int num_samples) {
3217 std::vector<Gap> gaps;
3220 std::vector<vec3> sample_directions = sampleDirectionsInCone(apex, central_axis, half_angle, num_samples);
3222 if (sample_directions.empty()) {
3227 std::vector<RaySample> ray_samples;
3228 ray_samples.reserve(sample_directions.size());
3230 float max_distance = (height > 0.0f) ? height : -1.0f;
3233 std::vector<RayQuery> ray_queries;
3234 ray_queries.reserve(sample_directions.size());
3236 for (
const vec3 &direction: sample_directions) {
3237 ray_queries.emplace_back(apex, direction, max_distance);
3241 RayTracingStats ray_stats;
3242 std::vector<HitResult> hit_results =
castRays(ray_queries, &ray_stats);
3245 for (
size_t i = 0; i < hit_results.size(); ++i) {
3247 sample.direction = sample_directions[i];
3249 if (hit_results[i].hit) {
3250 sample.distance = hit_results[i].distance;
3251 sample.is_free =
false;
3253 sample.distance = (max_distance > 0.0f) ? max_distance : 1000.0f;
3254 sample.is_free =
true;
3257 ray_samples.push_back(sample);
3263 std::vector<std::pair<float, size_t>> angular_positions;
3264 for (
size_t i = 0; i < ray_samples.size(); ++i) {
3265 if (ray_samples[i].is_free) {
3267 float dot_product = ray_samples[i].direction * central_axis;
3268 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
3269 float angular_from_center = acosf(dot_product);
3270 angular_positions.push_back({angular_from_center, i});
3274 if (angular_positions.empty()) {
3279 std::sort(angular_positions.begin(), angular_positions.end());
3282 std::vector<bool> processed(ray_samples.size(),
false);
3283 float min_gap_angular_size = half_angle * 0.05f;
3285 for (
size_t start = 0; start < angular_positions.size(); ++start) {
3286 size_t start_idx = angular_positions[start].second;
3287 if (processed[start_idx])
3291 new_gap.sample_indices.push_back(start_idx);
3292 processed[start_idx] =
true;
3295 std::vector<float> distances_to_start;
3296 for (
size_t j = 0; j < ray_samples.size(); ++j) {
3297 if (j != start_idx && ray_samples[j].is_free && !processed[j]) {
3298 float dot_product = ray_samples[start_idx].direction * ray_samples[j].direction;
3299 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
3300 float angular_distance = acosf(dot_product);
3301 distances_to_start.push_back(angular_distance);
3303 distances_to_start.push_back(999.0f);
3308 float sample_density = 2.0f * half_angle / sqrtf((
float) num_samples);
3309 float adaptive_threshold = sample_density * 3.0f;
3311 for (
size_t j = 0; j < ray_samples.size(); ++j) {
3312 if (j != start_idx && ray_samples[j].is_free && !processed[j] && distances_to_start[j] < adaptive_threshold) {
3313 new_gap.sample_indices.push_back(j);
3314 processed[j] =
true;
3319 if (new_gap.sample_indices.size() >= 5) {
3320 gaps.push_back(new_gap);
3325 for (Gap &gap: gaps) {
3327 vec3 center(0, 0, 0);
3328 for (
int idx: gap.sample_indices) {
3329 center = center + ray_samples[idx].direction;
3331 center = center / (float) gap.sample_indices.size();
3333 gap.center_direction = center;
3336 std::vector<RaySample> gap_samples;
3337 for (
int idx: gap.sample_indices) {
3338 gap_samples.push_back(ray_samples[idx]);
3340 gap.angular_size = calculateGapAngularSize(gap_samples, central_axis);
3343 float dot_product = gap.center_direction * central_axis;
3344 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
3345 gap.angular_distance = acosf(dot_product);
3350 if (gaps.size() > 10) {
3351 float max_angular_distance = half_angle * 0.8f;
3353 auto it = std::remove_if(gaps.begin(), gaps.end(), [max_angular_distance](
const Gap &gap) { return gap.angular_distance > max_angular_distance; });
3355 gaps.erase(it, gaps.end());
3358 if (gaps.size() < 3 && gaps.size() > 0) {
3360 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; });
3368float CollisionDetection::calculateGapAngularSize(
const std::vector<RaySample> &gap_samples,
const vec3 ¢ral_axis) {
3370 if (gap_samples.empty()) {
3375 float min_angle =
M_PI;
3376 float max_angle = 0.0f;
3378 for (
const RaySample &sample: gap_samples) {
3379 float dot_product = sample.direction * central_axis;
3380 dot_product = std::max(-1.0f, std::min(1.0f, dot_product));
3381 float angle = acosf(dot_product);
3383 min_angle = std::min(min_angle, angle);
3384 max_angle = std::max(max_angle, angle);
3388 float angular_width = max_angle - min_angle;
3392 float solid_angle =
M_PI * angular_width * angular_width;
3397void CollisionDetection::scoreGapsByFishEyeMetric(std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
3400 if (gaps.size() <= 10) {
3401 for (Gap &gap: gaps) {
3404 float size_score = log(1.0f + gap.angular_size * 100.0f);
3406 float distance_penalty = exp(gap.angular_distance * 2.0f);
3408 gap.score = size_score / distance_penalty;
3411 std::sort(gaps.begin(), gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
3417 const size_t max_gaps_needed = std::min(
size_t(5), gaps.size());
3420 for (Gap &gap: gaps) {
3424 float size_score = log(1.0f + gap.angular_size * 100.0f);
3427 float distance_penalty = exp(gap.angular_distance * 2.0f);
3430 gap.score = size_score / distance_penalty;
3434 std::partial_sort(gaps.begin(), gaps.begin() + max_gaps_needed, gaps.end(), [](
const Gap &a,
const Gap &b) { return a.score > b.score; });
3437 gaps.resize(max_gaps_needed);
3440helios::vec3 CollisionDetection::findOptimalGapDirection(
const std::vector<Gap> &gaps,
const vec3 ¢ral_axis) {
3444 vec3 result = central_axis;
3450 const Gap &best_gap = gaps[0];
3451 return best_gap.center_direction;
3458 std::vector<std::pair<uint, uint>> collision_pairs;
3462 for (
uint target_id: target_UUIDs) {
3463 if (primitive_centroids_cache.find(target_id) == primitive_centroids_cache.end()) {
3465 std::vector<vec3> vertices =
context->getPrimitiveVertices(target_id);
3466 if (!vertices.empty()) {
3468 for (
const vec3 &vertex: vertices) {
3469 centroid = centroid + vertex;
3471 centroid = centroid / float(vertices.size());
3472 primitive_centroids_cache[target_id] = centroid;
3478 for (
uint query_id: query_UUIDs) {
3480 std::vector<vec3> query_vertices =
context->getPrimitiveVertices(query_id);
3481 if (query_vertices.empty())
3485 for (
const vec3 &vertex: query_vertices) {
3486 query_centroid = query_centroid + vertex;
3488 query_centroid = query_centroid / float(query_vertices.size());
3491 for (
uint target_id: target_UUIDs) {
3492 if (query_id == target_id)
3495 auto target_centroid_it = primitive_centroids_cache.find(target_id);
3496 if (target_centroid_it != primitive_centroids_cache.end()) {
3497 vec3 target_centroid = target_centroid_it->second;
3498 float distance = (query_centroid - target_centroid).magnitude();
3500 if (distance <= max_distance) {
3502 std::vector<uint> single_query = {query_id};
3503 std::vector<uint> single_target = {target_id};
3504 std::vector<uint> empty_objects;
3506 std::vector<uint> collisions =
findCollisions(single_query, empty_objects, single_target, empty_objects);
3507 if (!collisions.empty()) {
3508 collision_pairs.push_back(std::make_pair(query_id, target_id));
3515 return collision_pairs;
3519 if (distance <= 0.0f) {
3520 helios_runtime_error(
"ERROR (CollisionDetection::setMaxCollisionDistance): Distance must be positive");
3523 max_collision_distance = distance;
3527 return max_collision_distance;
3532 std::vector<uint> filtered_UUIDs;
3535 std::vector<uint> candidates;
3536 if (candidate_UUIDs.empty()) {
3537 candidates =
context->getAllUUIDs();
3539 candidates = candidate_UUIDs;
3543 for (
uint candidate_id: candidates) {
3545 if (!
context->doesPrimitiveExist(candidate_id)) {
3551 auto cache_it = primitive_centroids_cache.find(candidate_id);
3552 if (cache_it != primitive_centroids_cache.end()) {
3553 centroid = cache_it->second;
3556 std::vector<vec3> vertices =
context->getPrimitiveVertices(candidate_id);
3557 if (vertices.empty())
3561 for (
const vec3 &vertex: vertices) {
3562 centroid = centroid + vertex;
3564 centroid = centroid / float(vertices.size());
3565 primitive_centroids_cache[candidate_id] = centroid;
3569 float distance = (query_center - centroid).magnitude();
3570 if (distance <= max_radius) {
3571 filtered_UUIDs.push_back(candidate_id);
3575 return filtered_UUIDs;
3585 if (ray_origins.size() != ray_directions.size()) {
3586 helios_runtime_error(
"ERROR (CollisionDetection::calculateVoxelRayPathLengths): ray_origins and ray_directions vectors must have same size");
3589 if (ray_origins.empty()) {
3590 warnings.
addWarning(
"no_rays_provided",
"No rays provided");
3591 warnings.
report(std::cerr);
3596 initializeVoxelData(grid_center, grid_size, grid_divisions);
3601 ensurePrimitiveCacheCurrent();
3604#ifdef HELIOS_CUDA_AVAILABLE
3607 bool gpu_success = calculateVoxelRayPathLengths_GPU(ray_origins, ray_directions);
3610 if (printmessages) {
3611 warnings.
addWarning(
"gpu_voxel_fallback",
"GPU voxel calculation failed, falling back to CPU");
3613 gpu_acceleration_enabled =
false;
3614 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3617 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3620 calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3623 warnings.
report(std::cerr);
3627 if (!validateVoxelIndices(ijk)) {
3628 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Invalid voxel indices");
3631 if (!voxel_data_initialized) {
3632 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelTransmissionProbability): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3635 if (use_flat_arrays) {
3636 size_t flat_idx = flatIndex(ijk);
3637 voxel_ray_counts_flat[flat_idx] = P_denom;
3638 voxel_transmitted_flat[flat_idx] = P_trans;
3640 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = P_denom;
3641 voxel_transmitted[ijk.
x][ijk.
y][ijk.
z] = P_trans;
3646 if (!validateVoxelIndices(ijk)) {
3647 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelTransmissionProbability): Invalid voxel indices");
3650 if (!voxel_data_initialized) {
3656 if (use_flat_arrays) {
3657 size_t flat_idx = flatIndex(ijk);
3658 P_denom = voxel_ray_counts_flat[flat_idx];
3659 P_trans = voxel_transmitted_flat[flat_idx];
3661 P_denom = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3662 P_trans = voxel_transmitted[ijk.
x][ijk.
y][ijk.
z];
3667 if (!validateVoxelIndices(ijk)) {
3671 if (!voxel_data_initialized) {
3672 helios_runtime_error(
"ERROR (CollisionDetection::setVoxelRbar): Voxel data not initialized. Call calculateVoxelRayPathLengths first.");
3675 if (use_flat_arrays) {
3676 size_t flat_idx = flatIndex(ijk);
3678 int ray_count = voxel_ray_counts_flat[flat_idx];
3679 if (ray_count == 0) {
3681 voxel_ray_counts_flat[flat_idx] = 1;
3683 voxel_path_lengths_flat[flat_idx] = r_bar *
static_cast<float>(ray_count);
3686 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3687 if (ray_count == 0) {
3689 voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z] = 1;
3691 voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] = r_bar *
static_cast<float>(ray_count);
3696 if (!validateVoxelIndices(ijk)) {
3700 if (!voxel_data_initialized) {
3704 if (use_flat_arrays) {
3705 size_t flat_idx = flatIndex(ijk);
3706 int ray_count = voxel_ray_counts_flat[flat_idx];
3707 if (ray_count == 0) {
3712 return voxel_path_lengths_flat[flat_idx] /
static_cast<float>(ray_count);
3714 int ray_count = voxel_ray_counts[ijk.
x][ijk.
y][ijk.
z];
3715 if (ray_count == 0) {
3720 return voxel_path_lengths[ijk.
x][ijk.
y][ijk.
z] /
static_cast<float>(ray_count);
3725 if (!validateVoxelIndices(ijk)) {
3726 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayHitCounts): Invalid voxel indices");
3729 if (!voxel_data_initialized) {
3736 if (use_flat_arrays) {
3737 size_t flat_idx = flatIndex(ijk);
3738 hit_before = voxel_hit_before_flat[flat_idx];
3739 hit_after = voxel_hit_after_flat[flat_idx];
3740 hit_inside = voxel_hit_inside_flat[flat_idx];
3742 hit_before = voxel_hit_before[ijk.
x][ijk.
y][ijk.
z];
3743 hit_after = voxel_hit_after[ijk.
x][ijk.
y][ijk.
z];
3744 hit_inside = voxel_hit_inside[ijk.
x][ijk.
y][ijk.
z];
3749 if (!validateVoxelIndices(ijk)) {
3750 helios_runtime_error(
"ERROR (CollisionDetection::getVoxelRayPathLengths): Invalid voxel indices");
3753 if (!voxel_data_initialized) {
3754 return std::vector<float>();
3757 if (use_flat_arrays) {
3759 size_t flat_idx = flatIndex(ijk);
3762 if (flat_idx >= voxel_individual_path_offsets.size() || flat_idx >= voxel_individual_path_counts.size()) {
3763 return std::vector<float>();
3766 size_t offset = voxel_individual_path_offsets[flat_idx];
3767 size_t count = voxel_individual_path_counts[flat_idx];
3770 if (count == 0 || offset + count > voxel_individual_path_lengths_flat.size()) {
3771 return std::vector<float>();
3774 std::vector<float> result;
3775 result.reserve(count);
3777 for (
size_t i = 0; i < count; ++i) {
3778 result.push_back(voxel_individual_path_lengths_flat[offset + i]);
3783 return voxel_individual_path_lengths[ijk.
x][ijk.
y][ijk.
z];
3788 voxel_ray_counts.clear();
3789 voxel_transmitted.clear();
3790 voxel_path_lengths.clear();
3791 voxel_hit_before.clear();
3792 voxel_hit_after.clear();
3793 voxel_hit_inside.clear();
3794 voxel_individual_path_lengths.clear();
3795 voxel_data_initialized =
false;
3797 if (printmessages) {
3798 std::cout <<
"Voxel data cleared." << std::endl;
3804void CollisionDetection::initializeVoxelData(
const vec3 &grid_center,
const vec3 &grid_size,
const helios::int3 &grid_divisions) {
3807 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 ||
3808 grid_divisions.
z != voxel_grid_divisions.
z;
3812 if (use_flat_arrays) {
3814 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3815 std::fill(voxel_ray_counts_flat.begin(), voxel_ray_counts_flat.end(), 0);
3816 std::fill(voxel_transmitted_flat.begin(), voxel_transmitted_flat.end(), 0);
3817 std::fill(voxel_path_lengths_flat.begin(), voxel_path_lengths_flat.end(), 0.0f);
3818 std::fill(voxel_hit_before_flat.begin(), voxel_hit_before_flat.end(), 0);
3819 std::fill(voxel_hit_after_flat.begin(), voxel_hit_after_flat.end(), 0);
3820 std::fill(voxel_hit_inside_flat.begin(), voxel_hit_inside_flat.end(), 0);
3823 voxel_individual_path_lengths_flat.clear();
3824 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
3825 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
3828 for (
int i = 0; i < grid_divisions.
x; i++) {
3829 for (
int j = 0; j < grid_divisions.
y; j++) {
3830 for (
int k = 0; k < grid_divisions.
z; k++) {
3831 voxel_ray_counts[i][j][k] = 0;
3832 voxel_transmitted[i][j][k] = 0;
3833 voxel_path_lengths[i][j][k] = 0.0f;
3834 voxel_hit_before[i][j][k] = 0;
3835 voxel_hit_after[i][j][k] = 0;
3836 voxel_hit_inside[i][j][k] = 0;
3837 voxel_individual_path_lengths[i][j][k].clear();
3846 voxel_grid_center = grid_center;
3847 voxel_grid_size = grid_size;
3848 voxel_grid_divisions = grid_divisions;
3851 use_flat_arrays =
true;
3853 if (use_flat_arrays) {
3855 size_t total_voxels =
static_cast<size_t>(grid_divisions.
x) * grid_divisions.
y * grid_divisions.
z;
3857 voxel_ray_counts_flat.assign(total_voxels, 0);
3858 voxel_transmitted_flat.assign(total_voxels, 0);
3859 voxel_path_lengths_flat.assign(total_voxels, 0.0f);
3860 voxel_hit_before_flat.assign(total_voxels, 0);
3861 voxel_hit_after_flat.assign(total_voxels, 0);
3862 voxel_hit_inside_flat.assign(total_voxels, 0);
3865 voxel_individual_path_lengths_flat.clear();
3866 voxel_individual_path_offsets.assign(total_voxels, 0);
3867 voxel_individual_path_counts.assign(total_voxels, 0);
3870 voxel_individual_path_lengths_flat.reserve(total_voxels * 10);
3874 voxel_ray_counts.resize(grid_divisions.
x);
3875 voxel_transmitted.resize(grid_divisions.
x);
3876 voxel_path_lengths.resize(grid_divisions.
x);
3877 voxel_hit_before.resize(grid_divisions.
x);
3878 voxel_hit_after.resize(grid_divisions.
x);
3879 voxel_hit_inside.resize(grid_divisions.
x);
3880 voxel_individual_path_lengths.resize(grid_divisions.
x);
3882 for (
int i = 0; i < grid_divisions.
x; i++) {
3883 voxel_ray_counts[i].resize(grid_divisions.
y);
3884 voxel_transmitted[i].resize(grid_divisions.
y);
3885 voxel_path_lengths[i].resize(grid_divisions.
y);
3886 voxel_hit_before[i].resize(grid_divisions.
y);
3887 voxel_hit_after[i].resize(grid_divisions.
y);
3888 voxel_hit_inside[i].resize(grid_divisions.
y);
3889 voxel_individual_path_lengths[i].resize(grid_divisions.
y);
3891 for (
int j = 0; j < grid_divisions.
y; j++) {
3892 voxel_ray_counts[i][j].resize(grid_divisions.
z, 0);
3893 voxel_transmitted[i][j].resize(grid_divisions.
z, 0);
3894 voxel_path_lengths[i][j].resize(grid_divisions.
z, 0.0f);
3895 voxel_hit_before[i][j].resize(grid_divisions.
z, 0);
3896 voxel_hit_after[i][j].resize(grid_divisions.
z, 0);
3897 voxel_hit_inside[i][j].resize(grid_divisions.
z, 0);
3898 voxel_individual_path_lengths[i][j].resize(grid_divisions.
z);
3903 voxel_data_initialized =
true;
3906bool CollisionDetection::validateVoxelIndices(
const helios::int3 &ijk)
const {
3907 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);
3910void CollisionDetection::calculateVoxelAABB(
const helios::int3 &ijk,
vec3 &voxel_min,
vec3 &voxel_max)
const {
3911 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));
3913 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3915 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);
3917 voxel_max = voxel_min + voxel_size;
3920std::vector<std::pair<helios::int3, float>> CollisionDetection::traverseVoxelGrid(
const vec3 &ray_origin,
const vec3 &ray_direction)
const {
3921 std::vector<std::pair<helios::int3, float>> traversed_voxels;
3924 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
3925 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
3926 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));
3929 float t_grid_min, t_grid_max;
3930 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max)) {
3931 return traversed_voxels;
3935 if (t_grid_max <= 1e-6) {
3936 return traversed_voxels;
3940 t_grid_min = std::max(0.0f, t_grid_min);
3943 if (voxel_grid_divisions.
x == 1 && voxel_grid_divisions.
y == 1 && voxel_grid_divisions.
z == 1) {
3944 float path_length = t_grid_max - t_grid_min;
3945 if (path_length > 1e-6f) {
3946 traversed_voxels.emplace_back(helios::make_int3(0, 0, 0), path_length);
3948 return traversed_voxels;
3952 vec3 start_pos = ray_origin + t_grid_min * ray_direction;
3956 current_voxel.
x =
static_cast<int>(std::floor((start_pos.
x - grid_min.
x) / voxel_size.
x));
3957 current_voxel.
y =
static_cast<int>(std::floor((start_pos.
y - grid_min.
y) / voxel_size.
y));
3958 current_voxel.
z =
static_cast<int>(std::floor((start_pos.
z - grid_min.
z) / voxel_size.
z));
3961 current_voxel.
x = std::max(0, std::min(current_voxel.
x, voxel_grid_divisions.
x - 1));
3962 current_voxel.
y = std::max(0, std::min(current_voxel.
y, voxel_grid_divisions.
y - 1));
3963 current_voxel.
z = std::max(0, std::min(current_voxel.
z, voxel_grid_divisions.
z - 1));
3967 vec3 t_delta, t_max;
3970 for (
int i = 0; i < 3; i++) {
3971 float dir_comp = (i == 0) ? ray_direction.
x : (i == 1) ? ray_direction.y : ray_direction.z;
3972 float size_comp = (i == 0) ? voxel_size.
x : (i == 1) ? voxel_size.y : voxel_size.z;
3973 float grid_min_comp = (i == 0) ? grid_min.
x : (i == 1) ? grid_min.y : grid_min.z;
3974 float start_comp = (i == 0) ? start_pos.
x : (i == 1) ? start_pos.y : start_pos.z;
3975 int current_comp = (i == 0) ? current_voxel.
x : (i == 1) ? current_voxel.y : current_voxel.z;
3976 int max_comp = (i == 0) ? voxel_grid_divisions.
x : (i == 1) ? voxel_grid_divisions.y : voxel_grid_divisions.z;
3978 if (std::abs(dir_comp) < 1e-8f) {
3984 }
else if (i == 1) {
3996 step.
x = (dir_comp > 0) ? 1 : -1;
3997 t_delta.
x = std::abs(size_comp / dir_comp);
4000 t_max.
x = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
4002 t_max.
x = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
4004 }
else if (i == 1) {
4005 step.
y = (dir_comp > 0) ? 1 : -1;
4006 t_delta.
y = std::abs(size_comp / dir_comp);
4009 t_max.
y = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
4011 t_max.
y = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
4014 step.
z = (dir_comp > 0) ? 1 : -1;
4015 t_delta.
z = std::abs(size_comp / dir_comp);
4018 t_max.
z = t_grid_min + (grid_min_comp + (current_comp + 1) * size_comp - start_comp) / dir_comp;
4020 t_max.
z = t_grid_min + (grid_min_comp + current_comp * size_comp - start_comp) / dir_comp;
4027 float current_t = t_grid_min;
4029 while (validateVoxelIndices(current_voxel) && current_t < t_grid_max) {
4031 float next_t = std::min({t_max.
x, t_max.
y, t_max.
z, t_grid_max});
4032 float path_length = next_t - current_t;
4034 if (path_length > 1e-6f) {
4035 traversed_voxels.emplace_back(current_voxel, path_length);
4039 if (next_t >= t_grid_max) {
4044 if (t_max.
x <= t_max.
y && t_max.
x <= t_max.
z) {
4045 current_voxel.
x += step.
x;
4046 t_max.
x += t_delta.
x;
4047 }
else if (t_max.
y <= t_max.
z) {
4048 current_voxel.
y += step.
y;
4049 t_max.
y += t_delta.
y;
4051 current_voxel.
z += step.
z;
4052 t_max.
z += t_delta.
z;
4058 return traversed_voxels;
4061void CollisionDetection::calculateVoxelRayPathLengths_CPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
4064 auto start_time = std::chrono::high_resolution_clock::now();
4067 std::atomic<long long> total_raycast_time(0);
4068 std::atomic<int> raycast_count(0);
4073 const int num_rays =
static_cast<int>(ray_origins.size());
4076 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
4079 vec3 grid_min = voxel_grid_center - 0.5f * voxel_grid_size;
4080 vec3 grid_max = voxel_grid_center + 0.5f * voxel_grid_size;
4083 const int num_threads = omp_get_max_threads();
4086 std::vector<std::vector<int>> thread_ray_counts(num_threads, std::vector<int>(total_voxels, 0));
4087 std::vector<std::vector<float>> thread_path_lengths(num_threads, std::vector<float>(total_voxels, 0.0f));
4088 std::vector<std::vector<int>> thread_hit_before(num_threads, std::vector<int>(total_voxels, 0));
4089 std::vector<std::vector<int>> thread_hit_after(num_threads, std::vector<int>(total_voxels, 0));
4090 std::vector<std::vector<int>> thread_hit_inside(num_threads, std::vector<int>(total_voxels, 0));
4091 std::vector<std::vector<int>> thread_transmitted(num_threads, std::vector<int>(total_voxels, 0));
4092 std::vector<std::vector<std::vector<float>>> thread_individual_paths(num_threads, std::vector<std::vector<float>>(total_voxels));
4095#pragma omp parallel for schedule(dynamic)
4096 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
4097 const int thread_id = omp_get_thread_num();
4098 const vec3 &ray_origin = ray_origins[ray_idx];
4099 const vec3 &ray_direction = ray_directions[ray_idx];
4102 float t_grid_min, t_grid_max;
4103 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
4108 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
4111 if (traversed_voxels.empty()) {
4116 auto raycast_start = std::chrono::high_resolution_clock::now();
4117 RayQuery query(ray_origin, ray_direction, -1.0f, {});
4118 HitResult hit =
castRay(query);
4119 auto raycast_end = std::chrono::high_resolution_clock::now();
4122 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
4125 float hit_distance = hit.hit ? hit.distance : 1e30f;
4128 for (
const auto &voxel_data: traversed_voxels) {
4130 float path_length = voxel_data.second;
4133 vec3 voxel_min, voxel_max;
4134 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
4138 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
4145 bool hit_before =
false;
4146 bool hit_after =
false;
4147 bool hit_inside =
false;
4151 if (hit_distance < t_min) {
4154 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
4168 size_t flat_idx = flatIndex(voxel_idx);
4171 thread_ray_counts[thread_id][flat_idx]++;
4172 thread_path_lengths[thread_id][flat_idx] += path_length;
4175 thread_hit_before[thread_id][flat_idx]++;
4178 thread_hit_after[thread_id][flat_idx]++;
4181 thread_hit_inside[thread_id][flat_idx]++;
4183 thread_transmitted[thread_id][flat_idx]++;
4187 thread_individual_paths[thread_id][flat_idx].push_back(path_length);
4193 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
4194 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4195 voxel_ray_counts_flat[voxel_idx] += thread_ray_counts[thread_id][voxel_idx];
4196 voxel_path_lengths_flat[voxel_idx] += thread_path_lengths[thread_id][voxel_idx];
4197 voxel_hit_before_flat[voxel_idx] += thread_hit_before[thread_id][voxel_idx];
4198 voxel_hit_after_flat[voxel_idx] += thread_hit_after[thread_id][voxel_idx];
4199 voxel_hit_inside_flat[voxel_idx] += thread_hit_inside[thread_id][voxel_idx];
4200 voxel_transmitted_flat[voxel_idx] += thread_transmitted[thread_id][voxel_idx];
4205 if (use_flat_arrays) {
4207 voxel_individual_path_lengths_flat.clear();
4210 size_t total_paths = 0;
4211 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
4212 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4213 total_paths += thread_individual_paths[thread_id][voxel_idx].size();
4216 voxel_individual_path_lengths_flat.reserve(total_paths);
4219 size_t current_offset = 0;
4220 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4221 voxel_individual_path_offsets[voxel_idx] = current_offset;
4222 size_t voxel_path_count = 0;
4225 for (
int thread_id = 0; thread_id < num_threads; ++thread_id) {
4226 for (
float path_length: thread_individual_paths[thread_id][voxel_idx]) {
4227 voxel_individual_path_lengths_flat.push_back(path_length);
4232 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
4233 current_offset += voxel_path_count;
4238 const int num_threads = 1;
4239 const int thread_id = 0;
4242 std::vector<int> serial_ray_counts(total_voxels, 0);
4243 std::vector<float> serial_path_lengths(total_voxels, 0.0f);
4244 std::vector<int> serial_hit_before(total_voxels, 0);
4245 std::vector<int> serial_hit_after(total_voxels, 0);
4246 std::vector<int> serial_hit_inside(total_voxels, 0);
4247 std::vector<int> serial_transmitted(total_voxels, 0);
4248 std::vector<std::vector<float>> serial_individual_paths(total_voxels);
4251 for (
int ray_idx = 0; ray_idx < num_rays; ray_idx++) {
4252 const vec3 &ray_origin = ray_origins[ray_idx];
4253 const vec3 &ray_direction = ray_directions[ray_idx];
4256 float t_grid_min, t_grid_max;
4257 if (!rayAABBIntersect(ray_origin, ray_direction, grid_min, grid_max, t_grid_min, t_grid_max) || t_grid_max <= 1e-6) {
4262 auto traversed_voxels = traverseVoxelGrid(ray_origin, ray_direction);
4265 if (traversed_voxels.empty()) {
4270 auto raycast_start = std::chrono::high_resolution_clock::now();
4271 RayQuery query(ray_origin, ray_direction, -1.0f, {});
4272 HitResult hit =
castRay(query);
4273 auto raycast_end = std::chrono::high_resolution_clock::now();
4276 total_raycast_time += std::chrono::duration_cast<std::chrono::microseconds>(raycast_end - raycast_start).count();
4279 float hit_distance = hit.hit ? hit.distance : 1e30f;
4282 for (
const auto &voxel_data: traversed_voxels) {
4284 float path_length = voxel_data.second;
4287 vec3 voxel_min, voxel_max;
4288 calculateVoxelAABB(voxel_idx, voxel_min, voxel_max);
4292 rayAABBIntersect(ray_origin, ray_direction, voxel_min, voxel_max, t_min, t_max);
4299 bool hit_before =
false;
4300 bool hit_after =
false;
4301 bool hit_inside =
false;
4305 if (hit_distance < t_min) {
4308 }
else if (hit_distance >= t_min && hit_distance <= t_max) {
4322 size_t flat_idx = flatIndex(voxel_idx);
4324 serial_ray_counts[flat_idx]++;
4325 serial_path_lengths[flat_idx] += path_length;
4328 serial_hit_before[flat_idx]++;
4331 serial_hit_after[flat_idx]++;
4334 serial_hit_inside[flat_idx]++;
4336 serial_transmitted[flat_idx]++;
4340 serial_individual_paths[flat_idx].push_back(path_length);
4345 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4346 voxel_ray_counts_flat[voxel_idx] += serial_ray_counts[voxel_idx];
4347 voxel_path_lengths_flat[voxel_idx] += serial_path_lengths[voxel_idx];
4348 voxel_hit_before_flat[voxel_idx] += serial_hit_before[voxel_idx];
4349 voxel_hit_after_flat[voxel_idx] += serial_hit_after[voxel_idx];
4350 voxel_hit_inside_flat[voxel_idx] += serial_hit_inside[voxel_idx];
4351 voxel_transmitted_flat[voxel_idx] += serial_transmitted[voxel_idx];
4355 if (use_flat_arrays) {
4357 voxel_individual_path_lengths_flat.clear();
4360 size_t total_paths = 0;
4361 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4362 total_paths += serial_individual_paths[voxel_idx].size();
4364 voxel_individual_path_lengths_flat.reserve(total_paths);
4367 size_t current_offset = 0;
4368 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4369 voxel_individual_path_offsets[voxel_idx] = current_offset;
4370 size_t voxel_path_count = serial_individual_paths[voxel_idx].size();
4373 for (
float path_length: serial_individual_paths[voxel_idx]) {
4374 voxel_individual_path_lengths_flat.push_back(path_length);
4377 voxel_individual_path_counts[voxel_idx] = voxel_path_count;
4378 current_offset += voxel_path_count;
4383 auto end_time = std::chrono::high_resolution_clock::now();
4384 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4387 long long avg_raycast_time = raycast_count > 0 ? total_raycast_time.load() / raycast_count.load() : 0;
4410#ifdef HELIOS_CUDA_AVAILABLE
4411bool CollisionDetection::calculateVoxelRayPathLengths_GPU(
const std::vector<vec3> &ray_origins,
const std::vector<vec3> &ray_directions) {
4418 if (printmessages) {
4421 auto start_time = std::chrono::high_resolution_clock::now();
4423 const int num_rays =
static_cast<int>(ray_origins.size());
4424 const int total_voxels = voxel_grid_divisions.
x * voxel_grid_divisions.
y * voxel_grid_divisions.
z;
4427 std::vector<float> h_ray_origins(num_rays * 3);
4428 std::vector<float> h_ray_directions(num_rays * 3);
4429 std::vector<int> h_voxel_ray_counts(total_voxels, 0);
4430 std::vector<float> h_voxel_path_lengths(total_voxels, 0.0f);
4431 std::vector<int> h_voxel_transmitted(total_voxels, 0);
4432 std::vector<int> h_voxel_hit_before(total_voxels, 0);
4433 std::vector<int> h_voxel_hit_after(total_voxels, 0);
4434 std::vector<int> h_voxel_hit_inside(total_voxels, 0);
4437 for (
int i = 0; i < num_rays; i++) {
4438 h_ray_origins[i * 3 + 0] = ray_origins[i].x;
4439 h_ray_origins[i * 3 + 1] = ray_origins[i].y;
4440 h_ray_origins[i * 3 + 2] = ray_origins[i].z;
4442 h_ray_directions[i * 3 + 0] = ray_directions[i].x;
4443 h_ray_directions[i * 3 + 1] = ray_directions[i].y;
4444 h_ray_directions[i * 3 + 2] = ray_directions[i].z;
4448 int primitive_count =
static_cast<int>(primitive_cache.size());
4451 bool gpu_success =
launchVoxelRayPathLengths(num_rays, h_ray_origins.data(), h_ray_directions.data(), voxel_grid_center.
x, voxel_grid_center.
y, voxel_grid_center.
z, voxel_grid_size.x, voxel_grid_size.y, voxel_grid_size.z, voxel_grid_divisions.
x,
4452 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(),
4453 h_voxel_hit_inside.data());
4461 if (use_flat_arrays) {
4463 voxel_ray_counts_flat = h_voxel_ray_counts;
4464 voxel_path_lengths_flat = h_voxel_path_lengths;
4465 voxel_transmitted_flat = h_voxel_transmitted;
4468 voxel_hit_before_flat = h_voxel_hit_before;
4469 voxel_hit_after_flat = h_voxel_hit_after;
4470 voxel_hit_inside_flat = h_voxel_hit_inside;
4475 voxel_individual_path_lengths_flat.clear();
4476 std::fill(voxel_individual_path_offsets.begin(), voxel_individual_path_offsets.end(), 0);
4477 std::fill(voxel_individual_path_counts.begin(), voxel_individual_path_counts.end(), 0);
4482 for (
int voxel_idx = 0; voxel_idx < total_voxels; ++voxel_idx) {
4483 voxel_individual_path_offsets[voxel_idx] = voxel_individual_path_lengths_flat.size();
4485 if (h_voxel_ray_counts[voxel_idx] > 0) {
4488 std::vector<float> estimated_paths;
4491 int voxel_z = voxel_idx % voxel_grid_divisions.
z;
4492 int voxel_y = (voxel_idx / voxel_grid_divisions.
z) % voxel_grid_divisions.
y;
4493 int voxel_x = voxel_idx / (voxel_grid_divisions.
y * voxel_grid_divisions.
z);
4496 vec3 voxel_size = voxel_grid_size;
4497 voxel_size.
x /= voxel_grid_divisions.
x;
4498 voxel_size.
y /= voxel_grid_divisions.
y;
4499 voxel_size.
z /= voxel_grid_divisions.
z;
4501 vec3 voxel_min = voxel_grid_center - voxel_grid_size * 0.5f;
4502 voxel_min.
x += voxel_x * voxel_size.
x;
4503 voxel_min.
y += voxel_y * voxel_size.
y;
4504 voxel_min.
z += voxel_z * voxel_size.
z;
4506 vec3 voxel_max = voxel_min + voxel_size;
4509 for (
int ray_idx = 0; ray_idx < num_rays; ++ray_idx) {
4510 vec3 ray_origin = ray_origins[ray_idx];
4511 vec3 ray_dir = ray_directions[ray_idx];
4515 float t_max = std::numeric_limits<float>::max();
4518 for (
int axis = 0; axis < 3; ++axis) {
4519 float origin_comp = (axis == 0) ? ray_origin.
x : (axis == 1) ? ray_origin.y : ray_origin.z;
4520 float dir_comp = (axis == 0) ? ray_dir.
x : (axis == 1) ? ray_dir.y : ray_dir.z;
4521 float min_comp = (axis == 0) ? voxel_min.
x : (axis == 1) ? voxel_min.y : voxel_min.z;
4522 float max_comp = (axis == 0) ? voxel_max.
x : (axis == 1) ? voxel_max.y : voxel_max.z;
4524 if (std::abs(dir_comp) < 1e-9f) {
4526 if (origin_comp < min_comp || origin_comp > max_comp) {
4531 float t1 = (min_comp - origin_comp) / dir_comp;
4532 float t2 = (max_comp - origin_comp) / dir_comp;
4537 t_min = std::max(t_min, t1);
4538 t_max = std::min(t_max, t2);
4546 if (t_max > t_min && t_max > 0.0f) {
4547 float entry_t = std::max(0.0f, t_min);
4548 float exit_t = t_max;
4549 float path_length = exit_t - entry_t;
4551 if (path_length > 1e-6f) {
4552 estimated_paths.push_back(path_length);
4558 for (
float path_length: estimated_paths) {
4559 voxel_individual_path_lengths_flat.push_back(path_length);
4561 voxel_individual_path_counts[voxel_idx] = estimated_paths.size();
4563 voxel_individual_path_counts[voxel_idx] = 0;
4569 for (
int i = 0; i < voxel_grid_divisions.
x; i++) {
4570 for (
int j = 0; j < voxel_grid_divisions.
y; j++) {
4571 for (
int k = 0; k < voxel_grid_divisions.
z; k++) {
4572 int flat_idx = i * voxel_grid_divisions.
y * voxel_grid_divisions.
z + j * voxel_grid_divisions.
z + k;
4573 voxel_ray_counts[i][j][k] = h_voxel_ray_counts[flat_idx];
4574 voxel_path_lengths[i][j][k] = h_voxel_path_lengths[flat_idx];
4575 voxel_transmitted[i][j][k] = h_voxel_transmitted[flat_idx];
4578 voxel_hit_before[i][j][k] = h_voxel_hit_before[flat_idx];
4579 voxel_hit_after[i][j][k] = h_voxel_hit_after[flat_idx];
4580 voxel_hit_inside[i][j][k] = h_voxel_hit_inside[flat_idx];
4583 voxel_individual_path_lengths[i][j][k].clear();
4584 if (h_voxel_ray_counts[flat_idx] > 0) {
4585 float avg_path_length = h_voxel_path_lengths[flat_idx] / h_voxel_ray_counts[flat_idx];
4586 for (
int ray = 0; ray < h_voxel_ray_counts[flat_idx]; ++ray) {
4587 voxel_individual_path_lengths[i][j][k].push_back(avg_path_length);
4595 auto end_time = std::chrono::high_resolution_clock::now();
4596 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4598 if (printmessages) {
4601 int total_ray_voxel_intersections = 0;
4602 for (
const auto &count: h_voxel_ray_counts) {
4603 total_ray_voxel_intersections += count;
4611void CollisionDetection::ensureOptimizedBVH() {
4614 if (soa_dirty || bvh_nodes_soa.node_count != bvh_nodes.size() || bvh_nodes_soa.aabb_mins.empty()) {
4616 if (bvh_nodes.empty()) {
4617 bvh_nodes_soa.clear();
4618 bvh_nodes_soa.node_count = 0;
4622 size_t node_count = bvh_nodes.size();
4625 bvh_nodes_soa.node_count = node_count;
4626 bvh_nodes_soa.aabb_mins.resize(node_count);
4627 bvh_nodes_soa.aabb_maxs.resize(node_count);
4628 bvh_nodes_soa.left_children.resize(node_count);
4629 bvh_nodes_soa.right_children.resize(node_count);
4630 bvh_nodes_soa.primitive_starts.resize(node_count);
4631 bvh_nodes_soa.primitive_counts.resize(node_count);
4632 bvh_nodes_soa.is_leaf_flags.resize(node_count);
4635 for (
size_t i = 0; i < node_count; ++i) {
4636 const BVHNode &node = bvh_nodes[i];
4639 bvh_nodes_soa.aabb_mins[i] = node.
aabb_min;
4640 bvh_nodes_soa.aabb_maxs[i] = node.
aabb_max;
4641 bvh_nodes_soa.left_children[i] = node.
left_child;
4642 bvh_nodes_soa.right_children[i] = node.
right_child;
4645 bvh_nodes_soa.primitive_starts[i] = node.primitive_start;
4646 bvh_nodes_soa.primitive_counts[i] = node.primitive_count;
4647 bvh_nodes_soa.is_leaf_flags[i] = node.is_leaf ? 1 : 0;
4655void CollisionDetection::updatePrimitiveAABBCache(
uint uuid) {
4657 if (!
context->doesPrimitiveExist(uuid)) {
4663 std::vector<vec3> vertices =
context->getPrimitiveVertices(uuid);
4664 if (vertices.empty()) {
4669 vec3 aabb_min = vertices[0];
4670 vec3 aabb_max = vertices[0];
4672 for (
const vec3 &vertex: vertices) {
4673 aabb_min.
x = std::min(aabb_min.
x, vertex.x);
4674 aabb_min.
y = std::min(aabb_min.
y, vertex.y);
4675 aabb_min.
z = std::min(aabb_min.
z, vertex.z);
4677 aabb_max.
x = std::max(aabb_max.
x, vertex.x);
4678 aabb_max.
y = std::max(aabb_max.
y, vertex.y);
4679 aabb_max.
z = std::max(aabb_max.
z, vertex.z);
4683 primitive_aabbs_cache[uuid] = std::make_pair(aabb_min, aabb_max);
4685 }
catch (
const std::exception &e) {
4686 if (printmessages) {
4687 std::cerr <<
"Warning: Failed to cache AABB for primitive " << uuid <<
": " << e.what() << std::endl;
4692void CollisionDetection::optimizedRebuildBVH(
const std::set<uint> &final_geometry) {
4694 std::vector<uint> final_primitives(final_geometry.begin(), final_geometry.end());
4697 for (
uint uuid: final_geometry) {
4698 if (primitive_aabbs_cache.find(uuid) == primitive_aabbs_cache.end()) {
4699 updatePrimitiveAABBCache(uuid);
4703 if (printmessages) {
4704 std::cout <<
"Optimized rebuild with " << final_primitives.size() <<
" primitives (using cached AABBs)" << std::endl;
4711 last_bvh_geometry = final_geometry;
4719 tree_based_bvh_enabled =
true;
4720 tree_isolation_distance = isolation_distance;
4724 tree_based_bvh_enabled =
false;
4725 tree_bvh_map.clear();
4726 object_to_tree_map.clear();
4730 return tree_based_bvh_enabled;
4734 if (static_obstacle_primitives.empty() || obstacle_spatial_grid_initialized) {
4739 obstacle_spatial_grid.cell_size = 20.0f;
4740 obstacle_spatial_grid.grid_cells.clear();
4743 for (
uint obstacle_prim: static_obstacle_primitives) {
4744 if (
context->doesPrimitiveExist(obstacle_prim)) {
4746 context->getPrimitiveBoundingBox(obstacle_prim, min_corner, max_corner);
4747 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4749 int64_t grid_key = obstacle_spatial_grid.getGridKey(prim_center.
x, prim_center.
y);
4750 obstacle_spatial_grid.grid_cells[grid_key].push_back(obstacle_prim);
4754 obstacle_spatial_grid_initialized =
true;
4757std::vector<uint> CollisionDetection::ObstacleSpatialGrid::getRelevantObstacles(
const helios::vec3 &position,
float radius)
const {
4758 std::vector<uint> relevant_obstacles;
4761 int32_t min_grid_x =
static_cast<int32_t
>(std::floor((position.
x - radius) / cell_size));
4762 int32_t max_grid_x =
static_cast<int32_t
>(std::floor((position.
x + radius) / cell_size));
4763 int32_t min_grid_y =
static_cast<int32_t
>(std::floor((position.
y - radius) / cell_size));
4764 int32_t max_grid_y =
static_cast<int32_t
>(std::floor((position.
y + radius) / cell_size));
4767 for (int32_t grid_x = min_grid_x; grid_x <= max_grid_x; ++grid_x) {
4768 for (int32_t grid_y = min_grid_y; grid_y <= max_grid_y; ++grid_y) {
4769 int64_t grid_key = (
static_cast<int64_t
>(grid_x) << 32) |
static_cast<uint32_t
>(grid_y);
4771 auto cell_it = grid_cells.find(grid_key);
4772 if (cell_it != grid_cells.end()) {
4774 relevant_obstacles.insert(relevant_obstacles.end(), cell_it->second.begin(), cell_it->second.end());
4779 return relevant_obstacles;
4783 if (!tree_based_bvh_enabled) {
4784 if (printmessages) {
4785 std::cout <<
"WARNING: Tree registration ignored - tree-based BVH not enabled" << std::endl;
4791 vec3 tree_center(0, 0, 0);
4792 vec3 aabb_min(1e30f, 1e30f, 1e30f);
4793 vec3 aabb_max(-1e30f, -1e30f, -1e30f);
4795 for (
uint prim_uuid: tree_primitives) {
4796 if (
context->doesPrimitiveExist(prim_uuid)) {
4797 vec3 prim_min, prim_max;
4798 context->getPrimitiveBoundingBox(prim_uuid, prim_min, prim_max);
4800 aabb_min.
x = std::min(aabb_min.
x, prim_min.
x);
4801 aabb_min.
y = std::min(aabb_min.
y, prim_min.
y);
4802 aabb_min.
z = std::min(aabb_min.
z, prim_min.
z);
4804 aabb_max.
x = std::max(aabb_max.
x, prim_max.
x);
4805 aabb_max.
y = std::max(aabb_max.
y, prim_max.
y);
4806 aabb_max.
z = std::max(aabb_max.
z, prim_max.
z);
4810 tree_center = (aabb_min + aabb_max) * 0.5f;
4811 float tree_radius = (aabb_max - aabb_min).magnitude() * 0.5f;
4814 TreeBVH &tree_bvh = tree_bvh_map[tree_object_id];
4815 tree_bvh.tree_object_id = tree_object_id;
4816 tree_bvh.tree_center = tree_center;
4817 tree_bvh.tree_radius = tree_radius;
4820 for (
uint prim_uuid: tree_primitives) {
4821 object_to_tree_map[prim_uuid] = tree_object_id;
4826 static_obstacle_primitives.clear();
4829 for (
uint prim_uuid: obstacle_primitives) {
4830 if (
context->doesPrimitiveExist(prim_uuid)) {
4831 static_obstacle_primitives.push_back(prim_uuid);
4836 obstacle_spatial_grid_initialized =
false;
4841 std::vector<uint> relevant_geometry;
4843 if (!tree_based_bvh_enabled) {
4845 return relevant_geometry;
4849 const float MAX_STATIC_OBSTACLE_DISTANCE = max_distance;
4851 if (obstacle_spatial_grid_initialized && !static_obstacle_primitives.empty()) {
4853 std::vector<uint> candidate_obstacles = obstacle_spatial_grid.getRelevantObstacles(query_position, MAX_STATIC_OBSTACLE_DISTANCE);
4857 for (
uint static_prim: candidate_obstacles) {
4858 if (
context->doesPrimitiveExist(static_prim)) {
4860 context->getPrimitiveBoundingBox(static_prim, min_corner, max_corner);
4861 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4862 float distance = (query_position - prim_center).magnitude();
4863 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4864 relevant_geometry.push_back(static_prim);
4871 for (
uint static_prim: static_obstacle_primitives) {
4872 if (
context->doesPrimitiveExist(static_prim)) {
4874 context->getPrimitiveBoundingBox(static_prim, min_corner, max_corner);
4875 helios::vec3 prim_center = (min_corner + max_corner) * 0.5f;
4876 float distance = (query_position - prim_center).magnitude();
4877 if (distance < MAX_STATIC_OBSTACLE_DISTANCE) {
4878 relevant_geometry.push_back(static_prim);
4885 uint source_tree_id = 0;
4886 if (!query_primitives.empty()) {
4888 auto it = object_to_tree_map.find(query_primitives[0]);
4889 if (it != object_to_tree_map.end()) {
4890 source_tree_id = it->second;
4895 if (source_tree_id == 0) {
4896 float min_distance = 1e30f;
4897 for (
const auto &tree_pair: tree_bvh_map) {
4898 const TreeBVH &tree = tree_pair.second;
4899 float distance = (query_position - tree.tree_center).magnitude();
4900 if (distance < min_distance) {
4901 min_distance = distance;
4902 source_tree_id = tree.tree_object_id;
4908 for (
const auto &tree_pair: tree_bvh_map) {
4909 const TreeBVH &tree = tree_pair.second;
4910 uint tree_id = tree_pair.first;
4912 if (tree_id == source_tree_id) {
4914 for (
uint prim_uuid: tree.primitive_indices) {
4915 if (
context->doesPrimitiveExist(prim_uuid)) {
4916 relevant_geometry.push_back(prim_uuid);
4921 float distance = (query_position - tree.tree_center).magnitude();
4922 float interaction_threshold = tree_isolation_distance + tree.tree_radius;
4924 if (distance < interaction_threshold) {
4926 for (
uint prim_uuid: tree.primitive_indices) {
4927 if (
context->doesPrimitiveExist(prim_uuid)) {
4928 relevant_geometry.push_back(prim_uuid);
4935 return relevant_geometry;