17#define DOCTEST_CONFIG_IMPLEMENT
22#include "doctest_utils.h"
25using namespace helios;
28 return helios::runDoctestWithValidation(argc, argv);
31namespace CollisionTests {
37 std::vector<uint> uuids;
38 for (
int i = 0; i < count; i++) {
39 float x = i * separation;
41 uuids.push_back(uuid);
50 std::vector<uint> uuids;
51 for (
int i = 0; i < count; i++) {
52 float angle = (2.0f *
M_PI * i) / count;
54 float x = center.x + radius * cos(angle);
55 float y = center.y + radius * sin(angle);
58 uuids.push_back(uuid);
73 std::vector<uint> uuids;
74 float half_gap = gap_width * 0.5f;
75 float half_height = wall_height * 0.5f;
78 float left_wall_end = gap_center.
x - half_gap;
79 if (left_wall_end > -5.0f) {
80 uint uuid1 =
context->addTriangle(
make_vec3(-5.0f, gap_center.
y - half_height, wall_distance),
make_vec3(left_wall_end, gap_center.
y - half_height, wall_distance),
make_vec3(-5.0f, gap_center.
y + half_height, wall_distance));
81 uint uuid2 =
context->addTriangle(
make_vec3(left_wall_end, gap_center.
y - half_height, wall_distance),
make_vec3(left_wall_end, gap_center.
y + half_height, wall_distance),
make_vec3(-5.0f, gap_center.
y + half_height, wall_distance));
82 uuids.push_back(uuid1);
83 uuids.push_back(uuid2);
87 float right_wall_start = gap_center.
x + half_gap;
88 if (right_wall_start < 5.0f) {
89 uint uuid3 =
context->addTriangle(
make_vec3(right_wall_start, gap_center.
y - half_height, wall_distance),
make_vec3(5.0f, gap_center.
y - half_height, wall_distance),
make_vec3(right_wall_start, gap_center.
y + half_height, wall_distance));
90 uint uuid4 =
context->addTriangle(
make_vec3(5.0f, gap_center.
y - half_height, wall_distance),
make_vec3(5.0f, gap_center.
y + half_height, wall_distance),
make_vec3(right_wall_start, gap_center.
y + half_height, wall_distance));
91 uuids.push_back(uuid3);
92 uuids.push_back(uuid4);
105 vec3 direction = gap_center - apex;
121 float dot = std::max(-1.0f, std::min(1.0f, actual * expected));
134 std::vector<uint> uuids;
135 float half_separation = gap_separation * 0.5f;
139 uuids.insert(uuids.end(), left_wall.begin(), left_wall.end());
143 uuids.insert(uuids.end(), right_wall.begin(), right_wall.end());
150DOCTEST_TEST_CASE(
"CollisionDetection Plugin Initialization") {
153 collision.disableMessages();
156 DOCTEST_CHECK_NOTHROW(collision.disableMessages());
157 DOCTEST_CHECK_NOTHROW(collision.enableMessages());
158 collision.disableMessages();
166 collision.enableGPUAcceleration();
167 gpu_enabled = collision.isGPUAccelerationEnabled();
172 collision.buildBVH();
175 DOCTEST_INFO(
"GPU acceleration capability test - actual hardware dependent");
177 DOCTEST_WARN(
"GPU acceleration is available and enabled on this system");
179 DOCTEST_WARN(
"GPU acceleration requested but not available - using CPU fallback");
183 DOCTEST_CHECK_NOTHROW(collision.disableGPUAcceleration());
184 DOCTEST_CHECK(collision.isGPUAccelerationEnabled() ==
false);
186 }
catch (std::exception &e) {
188 DOCTEST_WARN((std::string(
"GPU acceleration test failed (expected on non-NVIDIA systems): ") + e.what()).c_str());
191 DOCTEST_CHECK_NOTHROW(collision.disableGPUAcceleration());
192 DOCTEST_CHECK(collision.isGPUAccelerationEnabled() ==
false);
197DOCTEST_TEST_CASE(
"CollisionDetection GPU Availability Query") {
200 collision.disableMessages();
203 bool available_first =
false;
206 DOCTEST_CHECK(available_first == available_second);
211 collision.enableGPUAcceleration();
213 DOCTEST_CHECK(collision.isGPUAccelerationEnabled() == available_first);
216 DOCTEST_CHECK_NOTHROW(collision.disableGPUAcceleration());
217 DOCTEST_CHECK(collision.isGPUAccelerationEnabled() ==
false);
221DOCTEST_TEST_CASE(
"CollisionDetection BVH Construction") {
226 collision.disableMessages();
234 collision.buildBVH();
236 DOCTEST_CHECK(collision.isBVHValid() ==
true);
237 DOCTEST_CHECK(collision.getPrimitiveCount() == 3);
241DOCTEST_TEST_CASE(
"CollisionDetection Basic Collision Detection") {
246 collision.disableMessages();
247 collision.disableGPUAcceleration();
256 collision.buildBVH();
259 std::vector<uint> collisions1 = collision.findCollisions(UUID1);
262 bool found_UUID2 = std::find(collisions1.begin(), collisions1.end(), UUID2) != collisions1.end();
263 bool found_UUID3 = std::find(collisions1.begin(), collisions1.end(), UUID3) != collisions1.end();
265 DOCTEST_CHECK(found_UUID2 ==
true);
266 DOCTEST_CHECK(found_UUID3 ==
false);
270DOCTEST_TEST_CASE(
"CollisionDetection BVH Statistics") {
275 collision.disableMessages();
278 for (
int i = 0; i < 20; i++) {
282 collision.buildBVH();
284 size_t node_count, leaf_count, max_depth;
285 collision.getBVHStatistics(node_count, leaf_count, max_depth);
287 DOCTEST_CHECK(node_count > 0);
288 DOCTEST_CHECK(leaf_count > 0);
290 DOCTEST_CHECK(max_depth >= 0);
294DOCTEST_TEST_CASE(
"CollisionDetection Empty Geometry Handling") {
299 collision.disableMessages();
302 collision.buildBVH();
305 DOCTEST_CHECK(collision.getPrimitiveCount() == 0);
308 std::vector<uint> collisions = collision.findCollisions(std::vector<uint>{});
310 DOCTEST_CHECK(collisions.empty() ==
true);
314DOCTEST_TEST_CASE(
"CollisionDetection Invalid UUID Handling") {
319 collision.disableMessages();
324 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(999999), std::runtime_error);
328 collision.findCollisions(999999);
329 DOCTEST_FAIL(
"Expected exception was not thrown");
330 }
catch (
const std::runtime_error &e) {
331 std::string error_msg = e.what();
332 bool has_relevant_content = error_msg.find(
"UUID") != std::string::npos || error_msg.find(
"invalid") != std::string::npos;
333 DOCTEST_CHECK(has_relevant_content);
338DOCTEST_TEST_CASE(
"CollisionDetection GPU/CPU Mode Switching") {
343 collision.disableMessages();
348 collision.buildBVH();
351 std::vector<uint> gpu_results;
354 collision.enableGPUAcceleration();
355 gpu_results = collision.findCollisions(UUID1);
359 collision.disableGPUAcceleration();
360 std::vector<uint> cpu_results = collision.findCollisions(UUID1);
363 std::sort(gpu_results.begin(), gpu_results.end());
364 std::sort(cpu_results.begin(), cpu_results.end());
366 DOCTEST_CHECK(gpu_results == cpu_results);
370DOCTEST_TEST_CASE(
"CollisionDetection Null Context Error Handling") {
377 DOCTEST_FAIL(
"Expected exception was not thrown");
378 }
catch (
const std::runtime_error &e) {
379 std::string error_msg = e.what();
380 bool has_relevant_content = error_msg.find(
"context") != std::string::npos || error_msg.find(
"Context") != std::string::npos || error_msg.find(
"null") != std::string::npos;
381 DOCTEST_CHECK(has_relevant_content);
386DOCTEST_TEST_CASE(
"CollisionDetection Invalid UUIDs in BuildBVH") {
391 collision.disableMessages();
394 std::vector<uint> invalid_UUIDs = {999999, 888888};
396 DOCTEST_CHECK_THROWS_AS(collision.buildBVH(invalid_UUIDs), std::runtime_error);
400 collision.buildBVH(invalid_UUIDs);
401 DOCTEST_FAIL(
"Expected exception was not thrown");
402 }
catch (
const std::runtime_error &e) {
403 std::string error_msg = e.what();
404 bool has_relevant_content = error_msg.find(
"UUID") != std::string::npos || error_msg.find(
"invalid") != std::string::npos;
405 DOCTEST_CHECK(has_relevant_content);
410DOCTEST_TEST_CASE(
"CollisionDetection Primitive/Object Collision Detection") {
415 collision.disableMessages();
416 collision.disableGPUAcceleration();
425 collision.buildBVH();
428 std::vector<uint> primitive_UUIDs = {UUID1};
429 std::vector<uint> object_IDs = {objID};
431 DOCTEST_CHECK_NOTHROW(collision.findCollisions(primitive_UUIDs, object_IDs));
435DOCTEST_TEST_CASE(
"CollisionDetection Empty Input Handling") {
440 collision.disableMessages();
443 std::vector<uint> empty_primitives;
444 std::vector<uint> collisions1 = collision.findCollisions(empty_primitives);
447 std::vector<uint> empty_objects;
448 std::vector<uint> collisions2 = collision.findCollisions(empty_primitives, empty_objects);
450 DOCTEST_CHECK(collisions1.empty() ==
true);
451 DOCTEST_CHECK(collisions2.empty() ==
true);
455DOCTEST_TEST_CASE(
"CollisionDetection Invalid Object ID Error Handling") {
460 collision.disableMessages();
463 std::vector<uint> empty_primitives;
464 std::vector<uint> invalid_objects = {999999};
466 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(empty_primitives, invalid_objects), std::runtime_error);
470 collision.findCollisions(empty_primitives, invalid_objects);
471 DOCTEST_FAIL(
"Expected exception was not thrown");
472 }
catch (
const std::runtime_error &e) {
473 std::string error_msg = e.what();
474 bool has_relevant_content = error_msg.find(
"object") != std::string::npos || error_msg.find(
"Object") != std::string::npos || error_msg.find(
"999999") != std::string::npos || error_msg.find(
"exist") != std::string::npos ||
475 error_msg.find(
"invalid") != std::string::npos;
476 DOCTEST_CHECK(has_relevant_content);
481DOCTEST_TEST_CASE(
"CollisionDetection Manual BVH Rebuild") {
486 collision.disableMessages();
490 collision.buildBVH();
492 size_t initial_count = collision.getPrimitiveCount();
498 collision.rebuildBVH();
500 size_t final_count = collision.getPrimitiveCount();
502 DOCTEST_CHECK(final_count == 2);
506DOCTEST_TEST_CASE(
"CollisionDetection Message Control") {
511 collision.disableMessages();
514 DOCTEST_CHECK_NOTHROW(collision.disableMessages());
515 DOCTEST_CHECK_NOTHROW(collision.enableMessages());
516 collision.disableMessages();
520DOCTEST_TEST_CASE(
"CollisionDetection Large Geometry Handling") {
525 collision.disableMessages();
526 collision.disableGPUAcceleration();
529 for (
int i = 0; i < 50; i++) {
533 collision.buildBVH();
537 std::vector<uint> collisions = collision.findCollisions(UUID);
540 size_t node_count, leaf_count, max_depth;
541 collision.getBVHStatistics(node_count, leaf_count, max_depth);
543 DOCTEST_CHECK(collision.getPrimitiveCount() == 50);
544 DOCTEST_CHECK(node_count > 0);
546 DOCTEST_CHECK(max_depth >= 0);
550DOCTEST_TEST_CASE(
"CollisionDetection Single Primitive Edge Case") {
555 collision.disableMessages();
556 collision.disableGPUAcceleration();
561 collision.buildBVH();
564 std::vector<uint> collisions = collision.findCollisions(UUID1);
567 size_t node_count, leaf_count, max_depth;
568 collision.getBVHStatistics(node_count, leaf_count, max_depth);
570 DOCTEST_CHECK(collision.getPrimitiveCount() == 1);
571 DOCTEST_CHECK(collision.isBVHValid() ==
true);
574DOCTEST_TEST_CASE(
"CollisionDetection Texture Transparency Rejection") {
578 const char *texture =
"lib/images/disk_texture.png";
582 collision.disableMessages();
583 collision.disableGPUAcceleration();
587 DOCTEST_CHECK(
context.primitiveTextureHasTransparencyChannel(patch));
589 collision.buildBVH();
593 DOCTEST_CHECK(center_hit.
hit ==
true);
595 DOCTEST_CHECK(center_hit.
distance == doctest::Approx(5.0f));
599 DOCTEST_CHECK(corner_hit.
hit ==
false);
604 collision.buildBVH();
606 DOCTEST_CHECK(through_hit.
hit ==
true);
611DOCTEST_TEST_CASE(
"CollisionDetection Overlapping AABB Primitives") {
616 collision.disableMessages();
617 collision.disableGPUAcceleration();
624 collision.buildBVH();
627 std::vector<uint> collisions = collision.findCollisions(UUID1);
629 bool found_collision = std::find(collisions.begin(), collisions.end(), UUID2) != collisions.end();
631 DOCTEST_CHECK(found_collision ==
true);
635DOCTEST_TEST_CASE(
"CollisionDetection BVH Validity Persistence") {
640 collision.disableMessages();
643 DOCTEST_CHECK(collision.isBVHValid() ==
false);
647 collision.buildBVH();
650 DOCTEST_CHECK(collision.isBVHValid() ==
true);
654DOCTEST_TEST_CASE(
"CollisionDetection Soft/Hard Detection Integration - BVH Sharing") {
658 collision.disableMessages();
659 collision.disableGPUAcceleration();
667 std::vector<uint> all_geometry = {obstacle, soft_prim1, soft_prim2};
668 collision.buildBVH(all_geometry);
671 DOCTEST_CHECK(collision.isBVHValid() ==
true);
672 size_t initial_node_count, initial_leaf_count, initial_max_depth;
673 collision.getBVHStatistics(initial_node_count, initial_leaf_count, initial_max_depth);
676 std::vector<uint> soft_collisions = collision.findCollisions({soft_prim1, soft_prim2});
677 bool soft_detection_completed =
true;
683 vec3 obstacle_direction;
686 bool hard_hit = collision.findNearestSolidObstacleInCone(test_origin, test_direction, 0.52f, 1.0f, {obstacle}, distance, obstacle_direction);
689 DOCTEST_CHECK(soft_detection_completed ==
true);
690 DOCTEST_CHECK(hard_hit ==
true);
691 DOCTEST_CHECK(distance < 1.0f);
694 size_t final_node_count, final_leaf_count, final_max_depth;
695 collision.getBVHStatistics(final_node_count, final_leaf_count, final_max_depth);
697 DOCTEST_CHECK(initial_node_count == final_node_count);
698 DOCTEST_CHECK(initial_leaf_count == final_leaf_count);
699 DOCTEST_CHECK(collision.isBVHValid() ==
true);
703DOCTEST_TEST_CASE(
"CollisionDetection Soft/Hard Detection Integration - Sequential Calls") {
707 collision.disableMessages();
708 collision.disableGPUAcceleration();
715 std::vector<uint> all_obstacles = {ground, wall};
716 std::vector<uint> plant_parts = {plant_stem};
718 collision.buildBVH(all_obstacles);
721 std::vector<uint> soft_collisions = collision.findCollisions(plant_parts, {}, all_obstacles, {});
730 vec3 obstacle_direction;
731 bool hard_hit = collision.findNearestSolidObstacleInCone(growth_tip, growth_direction, 0.35f, 2.0f,
732 all_obstacles, distance, obstacle_direction);
735 std::vector<uint> soft_collisions_2 = collision.findCollisions(plant_parts, {}, all_obstacles, {});
741 bool hard_hit_2 = collision.findNearestSolidObstacleInCone(growth_tip, growth_direction_2, 0.35f, 0.5f, all_obstacles, distance, obstacle_direction);
744 DOCTEST_CHECK(soft_collisions.size() == soft_collisions_2.size());
745 DOCTEST_CHECK(hard_hit ==
true);
746 DOCTEST_CHECK(collision.isBVHValid() ==
true);
750DOCTEST_TEST_CASE(
"CollisionDetection Soft/Hard Detection Integration - Different Geometry Sets") {
754 collision.disableMessages();
755 collision.disableGPUAcceleration();
767 std::vector<uint> all_geometry = {hard_obstacle_1, hard_obstacle_2, soft_object_1, soft_object_2, shared_object};
768 collision.buildBVH(all_geometry);
775 std::vector<uint> hard_only = {hard_obstacle_1, hard_obstacle_2, shared_object};
777 vec3 obstacle_direction;
779 bool hard_hit = collision.findNearestSolidObstacleInCone(test_origin, test_direction, 0.4f, 2.0f, hard_only, distance, obstacle_direction);
782 std::vector<uint> soft_only = {soft_object_1, soft_object_2, shared_object};
783 std::vector<uint> soft_collisions = collision.findCollisions(soft_only);
789 bool hard_hit_2 = collision.findNearestSolidObstacleInCone(test_origin, test_direction_2, 0.4f, 10.0f,
790 soft_only, distance, obstacle_direction);
793 DOCTEST_CHECK(hard_hit ==
true);
794 DOCTEST_CHECK(hard_hit_2 ==
true);
795 DOCTEST_CHECK(collision.isBVHValid() ==
true);
798 DOCTEST_CHECK(collision.getPrimitiveCount() == all_geometry.size());
802DOCTEST_TEST_CASE(
"CollisionDetection Soft/Hard Detection Integration - BVH Rebuild Behavior") {
806 collision.disableMessages();
807 collision.disableGPUAcceleration();
811 std::vector<uint> initial_geometry = {obstacle1};
813 collision.buildBVH(initial_geometry);
819 vec3 obstacle_direction;
821 bool hit_initial = collision.findNearestSolidObstacleInCone(test_origin, test_direction, 0.3f, 1.0f, initial_geometry, distance, obstacle_direction);
827 std::vector<uint> expanded_geometry = {obstacle1, obstacle2, obstacle3};
830 collision.buildBVH(expanded_geometry);
833 std::vector<uint> soft_collisions = collision.findCollisions(expanded_geometry);
835 bool hit_after_rebuild = collision.findNearestSolidObstacleInCone(test_origin, test_direction, 0.3f, 1.0f, expanded_geometry, distance, obstacle_direction);
841 bool hit_subset = collision.findNearestSolidObstacleInCone(test_origin, test_direction_2, 0.5f, 2.0f, {obstacle2}, distance, obstacle_direction);
844 DOCTEST_CHECK(hit_initial ==
true);
845 DOCTEST_CHECK(hit_after_rebuild ==
true);
846 DOCTEST_CHECK(hit_subset ==
true);
847 DOCTEST_CHECK(collision.isBVHValid() ==
true);
848 DOCTEST_CHECK(collision.getPrimitiveCount() == expanded_geometry.size());
852DOCTEST_TEST_CASE(
"CollisionDetection GPU Acceleration") {
857 collision.disableMessages();
860 for (
int i = 0; i < 5; i++) {
866 collision.disableGPUAcceleration();
867 collision.buildBVH();
869 std::vector<uint> cpu_results = collision.findCollisions(UUID);
871 std::vector<uint> gpu_results;
874 collision.enableGPUAcceleration();
875 collision.buildBVH();
876 gpu_results = collision.findCollisions(UUID);
880 std::sort(cpu_results.begin(), cpu_results.end());
881 std::sort(gpu_results.begin(), gpu_results.end());
884 DOCTEST_INFO(
"GPU/CPU result comparison - GPU may not be available on this system");
885 if (cpu_results.size() == gpu_results.size()) {
886 bool results_match =
true;
887 for (
size_t i = 0; i < cpu_results.size(); i++) {
888 if (cpu_results[i] != gpu_results[i]) {
889 results_match =
false;
894 if (!results_match) {
895 DOCTEST_WARN(
"GPU/CPU results differ - may be expected if no CUDA device");
898 }
catch (std::exception &e) {
900 DOCTEST_WARN((std::string(
"GPU test failed (may be expected): ") + e.what()).c_str());
905DOCTEST_TEST_CASE(
"CollisionDetection GPU/CPU Message Display") {
910 collision.disableMessages();
914 collision.buildBVH();
917 collision.enableMessages();
918 collision.disableGPUAcceleration();
921 std::string cpu_output;
922 std::vector<uint> cpu_results;
925 cpu_results = collision.findCollisions(UUID1);
935 std::string gpu_output;
936 std::vector<uint> gpu_results;
940 collision.enableGPUAcceleration();
941 gpu_results = collision.findCollisions(UUID1);
950 collision.disableMessages();
952 std::string silent_output;
953 std::vector<uint> silent_results;
956 silent_results = collision.findCollisions(UUID1);
961 DOCTEST_CHECK(silent_output.find(
"Using GPU acceleration") == std::string::npos);
962 DOCTEST_CHECK(silent_output.find(
"Using CPU traversal") == std::string::npos);
966DOCTEST_TEST_CASE(
"CollisionDetection Automatic BVH Building") {
971 collision.disableMessages();
972 collision.disableGPUAcceleration();
978 DOCTEST_CHECK(collision.isBVHValid() ==
false);
981 std::vector<uint> results = collision.findCollisions(UUID1);
984 DOCTEST_CHECK(collision.isBVHValid() ==
true);
985 DOCTEST_CHECK(collision.getPrimitiveCount() == 1);
991 DOCTEST_CHECK(collision.isBVHValid() ==
false);
994 results = collision.findCollisions(UUID1);
997 DOCTEST_CHECK(collision.getPrimitiveCount() == 2);
998 DOCTEST_CHECK(collision.isBVHValid() ==
true);
1001 size_t count_before = collision.getPrimitiveCount();
1002 results = collision.findCollisions(UUID1);
1003 size_t count_after = collision.getPrimitiveCount();
1006 DOCTEST_CHECK(count_before == count_after);
1010DOCTEST_TEST_CASE(
"CollisionDetection Restricted Geometry - UUIDs Only") {
1015 collision.disableMessages();
1016 collision.disableGPUAcceleration();
1025 std::vector<uint> all_results = collision.findCollisions(UUID1);
1028 bool found_UUID2_all = std::find(all_results.begin(), all_results.end(), UUID2) != all_results.end();
1029 bool found_UUID3_all = std::find(all_results.begin(), all_results.end(), UUID3) != all_results.end();
1031 DOCTEST_CHECK(found_UUID2_all ==
true);
1032 DOCTEST_CHECK(found_UUID3_all ==
true);
1035 std::vector<uint> query_UUIDs = {UUID1};
1036 std::vector<uint> query_objects = {};
1037 std::vector<uint> target_UUIDs = {UUID2};
1038 std::vector<uint> target_objects = {};
1040 std::vector<uint> restricted_results = collision.findCollisions(query_UUIDs, query_objects, target_UUIDs, target_objects);
1043 bool found_UUID2_restricted = std::find(restricted_results.begin(), restricted_results.end(), UUID2) != restricted_results.end();
1044 bool found_UUID3_restricted = std::find(restricted_results.begin(), restricted_results.end(), UUID3) != restricted_results.end();
1046 DOCTEST_CHECK(found_UUID2_restricted ==
true);
1047 DOCTEST_CHECK(found_UUID3_restricted ==
false);
1051DOCTEST_TEST_CASE(
"CollisionDetection Restricted Geometry - Object IDs") {
1056 collision.disableMessages();
1057 collision.disableGPUAcceleration();
1067 std::vector<uint> query_UUIDs = {UUID1};
1068 std::vector<uint> query_objects = {};
1069 std::vector<uint> target_UUIDs = {};
1070 std::vector<uint> target_objects = {objID1};
1072 DOCTEST_CHECK_NOTHROW(collision.findCollisions(query_UUIDs, query_objects, target_UUIDs, target_objects));
1075 std::vector<uint> mixed_target_UUIDs = {UUID1};
1076 std::vector<uint> mixed_target_objects = {objID1};
1078 DOCTEST_CHECK_NOTHROW(collision.findCollisions(query_UUIDs, query_objects, mixed_target_UUIDs, mixed_target_objects));
1082DOCTEST_TEST_CASE(
"CollisionDetection Restricted Geometry - Error Handling") {
1087 collision.disableMessages();
1093 std::vector<uint> invalid_query_UUIDs = {999999};
1094 std::vector<uint> query_objects = {};
1095 std::vector<uint> valid_target_UUIDs = {UUID1};
1096 std::vector<uint> target_objects = {};
1098 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(invalid_query_UUIDs, query_objects, valid_target_UUIDs, target_objects), std::runtime_error);
1101 std::vector<uint> valid_query_UUIDs = {UUID1};
1102 std::vector<uint> invalid_target_UUIDs = {999999};
1104 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(valid_query_UUIDs, query_objects, invalid_target_UUIDs, target_objects), std::runtime_error);
1107 std::vector<uint> invalid_query_objects = {999999};
1109 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(valid_query_UUIDs, invalid_query_objects, valid_target_UUIDs, target_objects), std::runtime_error);
1112 std::vector<uint> invalid_target_objects = {999999};
1114 DOCTEST_CHECK_THROWS_AS(collision.findCollisions(valid_query_UUIDs, query_objects, valid_target_UUIDs, invalid_target_objects), std::runtime_error);
1118 collision.findCollisions(invalid_query_UUIDs, query_objects, valid_target_UUIDs, target_objects);
1119 DOCTEST_FAIL(
"Expected exception was not thrown");
1120 }
catch (
const std::runtime_error &e) {
1121 std::string error_msg = e.what();
1122 bool has_relevant_content = error_msg.find(
"UUID") != std::string::npos || error_msg.find(
"invalid") != std::string::npos;
1123 DOCTEST_CHECK(has_relevant_content);
1127DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Basic Functionality") {
1132 collision.disableMessages();
1142 float half_angle =
M_PI / 4.0f;
1154DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Gap Detection") {
1159 collision.disableMessages();
1169 float half_angle =
M_PI / 3.0f;
1186 DOCTEST_CHECK(gap_result.
confidence >= 0.0f);
1187 DOCTEST_CHECK(gap_result.
confidence <= 1.0f);
1188 DOCTEST_CHECK(dense_result.
confidence >= 0.0f);
1189 DOCTEST_CHECK(dense_result.
confidence <= 1.0f);
1190 DOCTEST_CHECK(sparse_result.
confidence >= 0.0f);
1191 DOCTEST_CHECK(sparse_result.
confidence <= 1.0f);
1194 float gap_deviation = acosf(std::max(-1.0f, std::min(1.0f, gap_result.
direction * central_axis)));
1195 float dense_deviation = acosf(std::max(-1.0f, std::min(1.0f, dense_result.
direction * central_axis)));
1197 DOCTEST_CHECK(gap_deviation >= 0.0f);
1198 DOCTEST_CHECK(dense_deviation >= 0.0f);
1201DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Edge Cases") {
1206 collision.disableMessages();
1215 DOCTEST_CHECK(result.
direction * central_axis > 0.9f);
1221 DOCTEST_CHECK(result.
direction * central_axis > 0.9f);
1227 DOCTEST_CHECK(result.
direction * central_axis > 0.9f);
1243DOCTEST_TEST_CASE(
"CollisionDetection Finite Cone Height") {
1248 collision.disableMessages();
1258 float half_angle =
M_PI / 6.0f;
1277DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Accuracy - Single Gap Tests") {
1280 collision.disableMessages();
1284 float half_angle =
M_PI / 4.0f;
1296 DOCTEST_CHECK(angular_error < 0.1f);
1304 collision_reset.disableMessages();
1313 DOCTEST_CHECK(angular_error < 0.15f);
1321 collision_reset.disableMessages();
1329 DOCTEST_CHECK(angular_error < 0.2f);
1334DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Accuracy - Symmetric Twin Gaps") {
1337 collision.disableMessages();
1341 float half_angle =
M_PI / 3.0f;
1350 float deviation_from_center = fabsf(acosf(std::max(-1.0f, std::min(1.0f, result.
direction * central_axis))));
1351 DOCTEST_CHECK(deviation_from_center < M_PI / 6.0f);
1359 collision_reset.disableMessages();
1371 DOCTEST_CHECK(angular_error < 0.3f);
1376DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Accuracy - Angular Precision") {
1379 collision.disableMessages();
1383 float half_angle =
M_PI / 4.0f;
1388 float expected_angle_from_center;
1389 std::string description;
1392 std::vector<TestCase> test_cases = {
1393 {
make_vec3(0, 0, 0), 0.0f,
"Center gap"},
1394 {
make_vec3(0.5f, 0, 0), 0.245f,
"15 degree gap"},
1395 {
make_vec3(-0.7f, 0, 0), -0.334f,
"Negative 19 degree gap"},
1396 {
make_vec3(0, 0.8f, 0), 0.381f,
"Vertical 22 degree gap"}
1399 for (
const auto &test_case: test_cases) {
1403 collision_fresh.disableMessages();
1411 vec3 gap_center_3d =
make_vec3(test_case.gap_position.x, test_case.gap_position.y, 2.0f);
1416 DOCTEST_CHECK_MESSAGE(angular_error < 0.35f, test_case.description.c_str());
1417 DOCTEST_CHECK_MESSAGE(result.
confidence > 0.1f, test_case.description.c_str());
1421DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Accuracy - Distance-Based Priority") {
1424 collision.disableMessages();
1428 float half_angle =
M_PI / 3.0f;
1445 DOCTEST_CHECK(angular_error_near < 0.5f);
1453 collision_reset.disableMessages();
1467 DOCTEST_CHECK(angular_error_near < 0.4f);
1475 collision_reset.disableMessages();
1497 DOCTEST_CHECK((error_near < 0.3f || error_far < 0.3f));
1501DOCTEST_TEST_CASE(
"CollisionDetection findOptimalConePath Accuracy - Edge Case Geometry") {
1504 collision.disableMessages();
1511 float half_angle =
M_PI / 6.0f;
1524 DOCTEST_CHECK(angular_error < 0.5f);
1531 collision_reset.disableMessages();
1533 float half_angle =
M_PI / 4.0f;
1535 float edge_angle = half_angle * 0.8f;
1536 float gap_x = 2.0f * tan(edge_angle);
1548 DOCTEST_CHECK(angular_error < 0.4f);
1555 collision_reset.disableMessages();
1557 float half_angle =
M_PI / 3.0f;
1575 DOCTEST_CHECK(angular_error < 0.6f);
1582 collision_reset.disableMessages();
1584 float half_angle =
M_PI / 2.5f;
1600 float deviation_from_center = acosf(std::max(-1.0f, std::min(1.0f, result.
direction * central_axis)));
1601 DOCTEST_CHECK(deviation_from_center <= half_angle + 0.01f);
1605DOCTEST_TEST_CASE(
"CollisionDetection Scale Test - 1000 Primitives") {
1610 collision.disableMessages();
1616 DOCTEST_CHECK_NOTHROW(collision.buildBVH());
1618 DOCTEST_CHECK(collision.isBVHValid() ==
true);
1619 DOCTEST_CHECK(collision.getPrimitiveCount() == 1000);
1622DOCTEST_TEST_CASE(
"CollisionDetection Scale Test - 10000 Primitives") {
1627 collision.disableMessages();
1633 DOCTEST_CHECK_NOTHROW(collision.buildBVH());
1635 DOCTEST_CHECK(collision.isBVHValid() ==
true);
1636 DOCTEST_CHECK(collision.getPrimitiveCount() == 10000);
1641DOCTEST_TEST_CASE(
"CollisionDetection CPU vs GPU Consistency - Small Scale") {
1652 cpu_collision.disableMessages();
1653 cpu_collision.disableGPUAcceleration();
1654 cpu_collision.buildBVH();
1658 gpu_collision.disableMessages();
1659 gpu_collision.enableGPUAcceleration();
1660 gpu_collision.buildBVH();
1663 for (
uint uuid: overlapping) {
1664 auto cpu_results = cpu_collision.findCollisions(uuid);
1665 auto gpu_results = gpu_collision.findCollisions(uuid);
1668 std::sort(cpu_results.begin(), cpu_results.end());
1669 std::sort(gpu_results.begin(), gpu_results.end());
1671 DOCTEST_CHECK(cpu_results == gpu_results);
1675DOCTEST_TEST_CASE(
"CollisionDetection CPU vs GPU Consistency - Large Scale") {
1680 collision.disableMessages();
1688 collision.disableGPUAcceleration();
1689 collision.buildBVH();
1690 auto cpu_results = collision.findCollisions(cluster1[0]);
1693 std::vector<uint> gpu_results;
1696 collision.enableGPUAcceleration();
1697 collision.buildBVH();
1698 gpu_results = collision.findCollisions(cluster1[0]);
1702 std::sort(cpu_results.begin(), cpu_results.end());
1703 std::sort(gpu_results.begin(), gpu_results.end());
1706 DOCTEST_CHECK(cpu_results == gpu_results);
1711DOCTEST_TEST_CASE(
"CollisionDetection Negative Test - Well Separated Primitives") {
1716 collision.disableMessages();
1723 collision.buildBVH();
1726 collision.disableGPUAcceleration();
1727 auto cpu_collisions = collision.findCollisions(triangle1);
1730 std::vector<uint> gpu_collisions;
1733 collision.enableGPUAcceleration();
1734 gpu_collisions = collision.findCollisions(triangle1);
1738 DOCTEST_CHECK(cpu_collisions.size() == 0);
1739 DOCTEST_CHECK(gpu_collisions.size() == 0);
1742DOCTEST_TEST_CASE(
"CollisionDetection Negative Test - Patch vs Distant Model") {
1747 collision.disableMessages();
1755 collision.buildBVH();
1758 collision.disableGPUAcceleration();
1759 auto cpu_collisions = collision.findCollisions(patch);
1761 std::vector<uint> gpu_collisions;
1764 collision.enableGPUAcceleration();
1765 gpu_collisions = collision.findCollisions(patch);
1769 DOCTEST_CHECK(cpu_collisions.size() == 0);
1770 DOCTEST_CHECK(gpu_collisions.size() == 0);
1771 DOCTEST_CHECK(cpu_collisions == gpu_collisions);
1776DOCTEST_TEST_CASE(
"CollisionDetection Edge Case - Boundary Touching") {
1781 collision.disableMessages();
1787 collision.buildBVH();
1790 collision.disableGPUAcceleration();
1791 auto cpu_results = collision.findCollisions(triangle1);
1793 std::vector<uint> gpu_results;
1796 collision.enableGPUAcceleration();
1797 gpu_results = collision.findCollisions(triangle1);
1800 std::sort(cpu_results.begin(), cpu_results.end());
1801 std::sort(gpu_results.begin(), gpu_results.end());
1803 DOCTEST_CHECK(cpu_results == gpu_results);
1806DOCTEST_TEST_CASE(
"CollisionDetection Edge Case - Very Small Overlaps") {
1811 collision.disableMessages();
1817 collision.buildBVH();
1820 collision.disableGPUAcceleration();
1821 auto cpu_results = collision.findCollisions(triangle1);
1823 std::vector<uint> gpu_results;
1826 collision.enableGPUAcceleration();
1827 gpu_results = collision.findCollisions(triangle1);
1830 std::sort(cpu_results.begin(), cpu_results.end());
1831 std::sort(gpu_results.begin(), gpu_results.end());
1833 DOCTEST_CHECK(cpu_results == gpu_results);
1838DOCTEST_TEST_CASE(
"CollisionDetection Real Geometry - PLY File Loading") {
1843 collision.disableMessages();
1847 std::vector<uint> complex_model;
1850 for (
int i = 0; i < 1000; i++) {
1851 float scale = 0.1f + (i % 10) * 0.05f;
1852 float angle = (i * 0.1f);
1853 float x = cos(angle) * (i * 0.01f);
1854 float y = sin(angle) * (i * 0.01f);
1855 float z = (i % 100) * 0.001f;
1858 complex_model.push_back(uuid);
1865 collision.buildBVH();
1868 collision.disableGPUAcceleration();
1869 auto cpu_intersecting = collision.findCollisions(patch_intersecting);
1870 auto cpu_non_intersecting = collision.findCollisions(patch_non_intersecting);
1872 std::vector<uint> gpu_intersecting, gpu_non_intersecting;
1875 collision.enableGPUAcceleration();
1876 gpu_intersecting = collision.findCollisions(patch_intersecting);
1877 gpu_non_intersecting = collision.findCollisions(patch_non_intersecting);
1881 std::sort(cpu_intersecting.begin(), cpu_intersecting.end());
1882 std::sort(gpu_intersecting.begin(), gpu_intersecting.end());
1883 std::sort(cpu_non_intersecting.begin(), cpu_non_intersecting.end());
1884 std::sort(gpu_non_intersecting.begin(), gpu_non_intersecting.end());
1886 DOCTEST_CHECK(cpu_intersecting == gpu_intersecting);
1887 DOCTEST_CHECK(cpu_non_intersecting == gpu_non_intersecting);
1888 DOCTEST_CHECK(cpu_non_intersecting.size() == 0);
1893DOCTEST_TEST_CASE(
"CollisionDetection Performance - BVH Construction Time") {
1898 collision.disableMessages();
1905 auto start = std::chrono::high_resolution_clock::now();
1906 collision.buildBVH();
1907 auto end = std::chrono::high_resolution_clock::now();
1909 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
1912 DOCTEST_CHECK(duration.count() < 5000);
1913 DOCTEST_CHECK(collision.isBVHValid() ==
true);
1918DOCTEST_TEST_CASE(
"CollisionDetection Memory Stress - Progressive Loading") {
1923 collision.disableMessages();
1926 std::vector<uint> all_uuids;
1928 for (
int batch = 0; batch < 10; batch++) {
1931 all_uuids.insert(all_uuids.end(), batch_uuids.begin(), batch_uuids.end());
1934 DOCTEST_CHECK_NOTHROW(collision.buildBVH());
1935 DOCTEST_CHECK(collision.isBVHValid() ==
true);
1936 DOCTEST_CHECK(collision.getPrimitiveCount() == all_uuids.size());
1942DOCTEST_TEST_CASE(
"CollisionDetection findNearestPrimitiveDistance - Basic Functionality") {
1945 collision.disableMessages();
1955 std::vector<uint> candidate_UUIDs = {triangle1, triangle2, triangle3};
1957 vec3 obstacle_direction;
1959 bool result = collision.findNearestPrimitiveDistance(origin, direction, candidate_UUIDs, distance, obstacle_direction);
1960 DOCTEST_CHECK(result ==
true);
1961 DOCTEST_CHECK(distance >= 4.0f);
1962 DOCTEST_CHECK(distance <= 6.0f);
1966 bool result_miss = collision.findNearestPrimitiveDistance(origin, direction_miss, candidate_UUIDs, distance, obstacle_direction);
1967 DOCTEST_CHECK(result_miss ==
false);
1970 std::vector<uint> subset_UUIDs = {triangle2, triangle3};
1971 bool result_subset = collision.findNearestPrimitiveDistance(origin, direction, subset_UUIDs, distance, obstacle_direction);
1972 DOCTEST_CHECK(result_subset ==
true);
1973 DOCTEST_CHECK(distance >= 9.0f);
1974 DOCTEST_CHECK(distance <= 11.0f);
1977DOCTEST_TEST_CASE(
"CollisionDetection findNearestPrimitiveDistance - Edge Cases") {
1980 collision.disableMessages();
1989 std::vector<uint> empty_UUIDs;
1990 vec3 obstacle_direction_unused;
1991 bool result_empty = collision.findNearestPrimitiveDistance(origin, direction, empty_UUIDs, distance, obstacle_direction_unused);
1992 DOCTEST_CHECK(result_empty ==
false);
1996 std::vector<uint> valid_UUIDs = {triangle1};
1997 bool result_non_norm = collision.findNearestPrimitiveDistance(origin, non_normalized_dir, valid_UUIDs, distance, obstacle_direction_unused);
1998 DOCTEST_CHECK(result_non_norm ==
false);
2001 std::vector<uint> invalid_UUIDs = {999999};
2002 bool result_invalid = collision.findNearestPrimitiveDistance(origin, direction, invalid_UUIDs, distance, obstacle_direction_unused);
2003 DOCTEST_CHECK(result_invalid ==
false);
2006 std::vector<uint> mixed_UUIDs = {triangle1, 999999};
2007 bool result_mixed = collision.findNearestPrimitiveDistance(origin, direction, mixed_UUIDs, distance, obstacle_direction_unused);
2008 DOCTEST_CHECK(result_mixed ==
true);
2009 DOCTEST_CHECK(distance >= 4.0f);
2010 DOCTEST_CHECK(distance <= 6.0f);
2013DOCTEST_TEST_CASE(
"CollisionDetection findNearestPrimitiveDistance - Complex Scenarios") {
2016 collision.disableMessages();
2025 vec3 obstacle_direction_unused;
2027 bool result = collision.findNearestPrimitiveDistance(origin, direction, cluster, distance, obstacle_direction_unused);
2029 DOCTEST_CHECK(result ==
false);
2035 bool result_near = collision.findNearestPrimitiveDistance(origin_near, direction_out, cluster, distance, obstacle_direction_unused);
2036 DOCTEST_CHECK(result_near ==
false);
2041 bool result_perpendicular = collision.findNearestPrimitiveDistance(origin_above, direction_down, cluster, distance, obstacle_direction_unused);
2042 DOCTEST_CHECK(result_perpendicular ==
true);
2043 DOCTEST_CHECK(distance >= 0.9f);
2044 DOCTEST_CHECK(distance <= 1.1f);
2047DOCTEST_TEST_CASE(
"CollisionDetection findNearestPrimitiveDistance - Directional Testing") {
2050 collision.disableMessages();
2058 std::vector<uint> all_triangles = {triangle_x, triangle_y, triangle_z, triangle_neg_x};
2063 struct DirectionTest {
2070 std::vector<DirectionTest> tests = {
2074 {
make_vec3(-1, 0, 0), 4.0f, 6.0f,
true},
2075 {
make_vec3(0.707f, 0.707f, 0), 6.0f, 8.0f,
false},
2078 vec3 obstacle_direction_unused;
2079 for (
const auto &test: tests) {
2080 bool result = collision.findNearestPrimitiveDistance(origin, test.direction, all_triangles, distance, obstacle_direction_unused);
2081 DOCTEST_CHECK(result == test.should_hit);
2082 if (result && test.should_hit) {
2083 DOCTEST_CHECK(distance >= test.expected_min);
2084 DOCTEST_CHECK(distance <= test.expected_max);
2089DOCTEST_TEST_CASE(
"CollisionDetection - findNearestPrimitiveDistance front/back face detection") {
2092 collision.disableMessages();
2097 uint horizontal_patch =
context.addPatch(patch_center, patch_size);
2099 std::vector<uint> candidates = {horizontal_patch};
2101 vec3 obstacle_direction;
2107 bool found_below = collision.findNearestPrimitiveDistance(origin_below, direction_up, candidates, distance, obstacle_direction);
2108 DOCTEST_CHECK(found_below ==
true);
2109 DOCTEST_CHECK(distance >= 0.49f);
2110 DOCTEST_CHECK(distance <= 0.51f);
2112 DOCTEST_CHECK(obstacle_direction.
z > 0.9f);
2113 DOCTEST_CHECK(std::abs(obstacle_direction.
x) < 0.1f);
2114 DOCTEST_CHECK(std::abs(obstacle_direction.
y) < 0.1f);
2120 bool found_above = collision.findNearestPrimitiveDistance(origin_above, direction_down, candidates, distance, obstacle_direction);
2121 DOCTEST_CHECK(found_above ==
true);
2122 DOCTEST_CHECK(distance >= 0.49f);
2123 DOCTEST_CHECK(distance <= 0.51f);
2125 DOCTEST_CHECK(obstacle_direction.
z < -0.9f);
2126 DOCTEST_CHECK(std::abs(obstacle_direction.
x) < 0.1f);
2127 DOCTEST_CHECK(std::abs(obstacle_direction.
y) < 0.1f);
2133 bool found_away = collision.findNearestPrimitiveDistance(origin_below2, direction_away, candidates, distance, obstacle_direction);
2134 DOCTEST_CHECK(found_away ==
false);
2139DOCTEST_TEST_CASE(
"CollisionDetection Cone-Based Obstacle Detection - Basic Functionality") {
2142 collision.disableMessages();
2146 std::vector<uint> obstacles = {obstacle_uuid};
2147 collision.buildBVH(obstacles);
2152 float half_angle =
deg2rad(30.0f);
2153 float height = 1.0f;
2156 vec3 obstacle_direction;
2158 bool found = collision.findNearestSolidObstacleInCone(apex, axis, half_angle, height, obstacles, distance, obstacle_direction);
2160 DOCTEST_CHECK(found ==
true);
2161 DOCTEST_CHECK(distance >= 0.49f);
2162 DOCTEST_CHECK(distance <= 0.51f);
2163 DOCTEST_CHECK(obstacle_direction.
z > 0.9f);
2167 bool found_far = collision.findNearestSolidObstacleInCone(apex_far, axis, half_angle, height, obstacles, distance, obstacle_direction);
2168 DOCTEST_CHECK(found_far ==
false);
2171 float narrow_angle =
deg2rad(5.0f);
2175 bool found_narrow = collision.findNearestSolidObstacleInCone(apex, axis_offset, narrow_angle, height, obstacles, distance, obstacle_direction);
2176 DOCTEST_CHECK(found_narrow ==
false);
2179DOCTEST_TEST_CASE(
"CollisionDetection Cone-Based vs Legacy Method Comparison") {
2182 collision.disableMessages();
2186 std::vector<uint> obstacles = {obstacle_uuid};
2187 collision.buildBVH(obstacles);
2194 float legacy_distance;
2195 vec3 legacy_obstacle_direction;
2196 bool legacy_found = collision.findNearestPrimitiveDistance(origin, direction, obstacles, legacy_distance, legacy_obstacle_direction);
2199 float cone_distance;
2200 vec3 cone_obstacle_direction;
2201 float half_angle =
deg2rad(30.0f);
2202 float height = 2.0f;
2203 bool cone_found = collision.findNearestSolidObstacleInCone(origin, direction, half_angle, height, obstacles, cone_distance, cone_obstacle_direction);
2206 DOCTEST_CHECK(legacy_found ==
true);
2207 DOCTEST_CHECK(cone_found ==
true);
2210 DOCTEST_CHECK(std::abs(cone_distance - 0.8f) < 0.04f);
2211 DOCTEST_CHECK(std::abs(legacy_distance - 0.8f) < 0.04f);
2214 DOCTEST_CHECK(legacy_obstacle_direction.
magnitude() > 0.9f);
2215 DOCTEST_CHECK(cone_obstacle_direction.
magnitude() > 0.9f);
2218DOCTEST_TEST_CASE(
"CollisionDetection Cone-Based Triangle vs Patch Intersection") {
2221 collision.disableMessages();
2229 std::vector<uint> triangle_obstacles = {triangle_uuid};
2230 std::vector<uint> patch_obstacles = {patch_uuid};
2232 collision.buildBVH({triangle_uuid, patch_uuid});
2236 float half_angle =
deg2rad(30.0f);
2237 float height = 1.0f;
2239 vec3 obstacle_direction;
2242 bool triangle_found = collision.findNearestSolidObstacleInCone(apex, axis, half_angle, height, triangle_obstacles, distance, obstacle_direction);
2243 DOCTEST_CHECK(triangle_found ==
true);
2244 DOCTEST_CHECK(distance > 0.4f);
2245 DOCTEST_CHECK(distance < 0.6f);
2249 bool patch_found = collision.findNearestSolidObstacleInCone(apex_patch, axis, half_angle, height, patch_obstacles, distance, obstacle_direction);
2250 DOCTEST_CHECK(patch_found ==
true);
2251 DOCTEST_CHECK(distance > 0.4f);
2252 DOCTEST_CHECK(distance < 0.6f);
2255DOCTEST_TEST_CASE(
"CollisionDetection Cone-Based Parameter Validation") {
2258 collision.disableMessages();
2261 std::vector<uint> obstacles = {obstacle_uuid};
2262 collision.buildBVH(obstacles);
2267 vec3 obstacle_direction;
2270 bool result1 = collision.findNearestSolidObstacleInCone(apex, axis, -0.1f, 1.0f, obstacles, distance, obstacle_direction);
2271 DOCTEST_CHECK(result1 ==
false);
2273 bool result2 = collision.findNearestSolidObstacleInCone(apex, axis, M_PI, 1.0f, obstacles, distance, obstacle_direction);
2274 DOCTEST_CHECK(result2 ==
false);
2276 bool result3 = collision.findNearestSolidObstacleInCone(apex, axis,
deg2rad(30.0f), -1.0f, obstacles, distance, obstacle_direction);
2277 DOCTEST_CHECK(result3 ==
false);
2280 std::vector<uint> empty_obstacles;
2281 bool result4 = collision.findNearestSolidObstacleInCone(apex, axis,
deg2rad(30.0f), 1.0f, empty_obstacles, distance, obstacle_direction);
2282 DOCTEST_CHECK(result4 ==
false);
2285 bool result5 = collision.findNearestSolidObstacleInCone(apex, axis,
deg2rad(30.0f), 1.0f, obstacles, distance, obstacle_direction);
2286 DOCTEST_CHECK(result5 ==
true);
2290DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Basic Functionality") {
2293 collision.disableMessages();
2296 vec3 grid_center(0, 0, 0);
2297 vec3 grid_size(10, 10, 10);
2298 int3 grid_divisions(2, 2, 2);
2301 std::vector<vec3> ray_origins;
2302 std::vector<vec3> ray_directions;
2305 ray_origins.push_back(
make_vec3(-10, 0, 0));
2306 ray_directions.push_back(
make_vec3(1, 0, 0));
2309 ray_origins.push_back(
make_vec3(-10, -10, -10));
2310 ray_directions.push_back(normalize(
make_vec3(1, 1, 1)));
2313 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2316 int P_denom, P_trans;
2317 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2318 DOCTEST_CHECK(P_denom >= 0);
2319 DOCTEST_CHECK(P_trans >= 0);
2320 DOCTEST_CHECK(P_trans <= P_denom);
2323 float r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2324 DOCTEST_CHECK(r_bar >= 0.0f);
2327 collision.clearVoxelData();
2330DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Edge Cases") {
2333 collision.disableMessages();
2336 vec3 grid_center(0, 0, 0);
2337 vec3 grid_size(5, 5, 5);
2338 int3 grid_divisions(1, 1, 1);
2340 std::vector<vec3> empty_origins;
2341 std::vector<vec3> empty_directions;
2344 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, empty_origins, empty_directions);
2347 int P_denom, P_trans;
2353 collision.getVoxelTransmissionProbability(
make_int3(-1, 0, 0), P_denom, P_trans);
2354 }
catch (
const std::exception &e) {
2359 collision.getVoxelTransmissionProbability(
make_int3(1, 0, 0), P_denom, P_trans);
2360 }
catch (
const std::exception &e) {
2366 DOCTEST_CHECK(
true);
2369DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Data Consistency") {
2372 collision.disableMessages();
2374 vec3 grid_center(0, 0, 0);
2375 vec3 grid_size(6, 6, 6);
2376 int3 grid_divisions(3, 3, 3);
2379 std::vector<vec3> ray_origins;
2380 std::vector<vec3> ray_directions;
2383 for (
int i = -1; i <= 1; i++) {
2384 for (
int j = -1; j <= 1; j++) {
2385 ray_origins.push_back(
make_vec3(i * 1.5f, j * 1.5f, -10));
2386 ray_directions.push_back(
make_vec3(0, 0, 1));
2391 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2394 bool found_data =
false;
2395 for (
int i = 0; i < grid_divisions.x; i++) {
2396 for (
int j = 0; j < grid_divisions.y; j++) {
2397 for (
int k = 0; k < grid_divisions.z; k++) {
2398 int P_denom, P_trans;
2399 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
2404 DOCTEST_CHECK(P_trans <= P_denom);
2407 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
2408 DOCTEST_CHECK(r_bar > 0.0f);
2414 DOCTEST_CHECK(found_data);
2417DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Manual Data Setting") {
2420 collision.disableMessages();
2422 vec3 grid_center(0, 0, 0);
2423 vec3 grid_size(4, 4, 4);
2424 int3 grid_divisions(2, 2, 2);
2427 std::vector<vec3> init_origins;
2428 std::vector<vec3> init_directions;
2429 init_origins.push_back(
make_vec3(0, 0, -10));
2430 init_directions.push_back(
make_vec3(0, 0, 1));
2431 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, init_origins, init_directions);
2434 int3 test_voxel(0, 0, 0);
2435 collision.setVoxelTransmissionProbability(100, 75, test_voxel);
2436 collision.setVoxelRbar(2.5f, test_voxel);
2439 int P_denom, P_trans;
2440 collision.getVoxelTransmissionProbability(test_voxel, P_denom, P_trans);
2441 DOCTEST_CHECK(P_denom == 100);
2442 DOCTEST_CHECK(P_trans == 75);
2444 float r_bar = collision.getVoxelRbar(test_voxel);
2445 DOCTEST_CHECK(std::abs(r_bar - 2.5f) < 1e-6f);
2448 int3 test_voxel2(1, 1, 1);
2449 collision.setVoxelTransmissionProbability(200, 150, test_voxel2);
2450 collision.setVoxelRbar(3.7f, test_voxel2);
2452 collision.getVoxelTransmissionProbability(test_voxel2, P_denom, P_trans);
2453 DOCTEST_CHECK(P_denom == 200);
2454 DOCTEST_CHECK(P_trans == 150);
2456 r_bar = collision.getVoxelRbar(test_voxel2);
2457 DOCTEST_CHECK(std::abs(r_bar - 3.7f) < 1e-6f);
2460 collision.getVoxelTransmissionProbability(test_voxel, P_denom, P_trans);
2461 DOCTEST_CHECK(P_denom == 100);
2462 DOCTEST_CHECK(P_trans == 75);
2465DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Different Grid Sizes") {
2468 collision.disableMessages();
2471 std::vector<int3> test_grids = {
2478 for (
const auto &grid_div: test_grids) {
2479 vec3 grid_center(0, 0, 0);
2480 vec3 grid_size(10, 10, 10);
2482 std::vector<vec3> ray_origins;
2483 std::vector<vec3> ray_directions;
2486 ray_origins.push_back(
make_vec3(0, 0, -10));
2487 ray_directions.push_back(
make_vec3(0, 0, 1));
2489 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_div, ray_origins, ray_directions);
2492 bool found_valid_voxel =
false;
2493 for (
int i = 0; i < grid_div.x; i++) {
2494 for (
int j = 0; j < grid_div.y; j++) {
2495 for (
int k = 0; k < grid_div.z; k++) {
2496 int P_denom, P_trans;
2497 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
2498 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
2501 DOCTEST_CHECK(P_denom >= 0);
2502 DOCTEST_CHECK(P_trans >= 0);
2503 DOCTEST_CHECK(r_bar >= 0.0f);
2504 found_valid_voxel =
true;
2508 DOCTEST_CHECK(found_valid_voxel);
2510 collision.clearVoxelData();
2514DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Ray Direction Variations") {
2517 collision.disableMessages();
2519 vec3 grid_center(0, 0, 0);
2520 vec3 grid_size(8, 8, 8);
2521 int3 grid_divisions(2, 2, 2);
2524 std::vector<vec3> test_directions = {
2533 for (
const auto &direction: test_directions) {
2534 std::vector<vec3> ray_origins;
2535 std::vector<vec3> ray_directions;
2538 vec3 start_point = grid_center - direction * 10.0f;
2539 ray_origins.push_back(start_point);
2540 ray_directions.push_back(direction);
2542 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2545 bool found_intersections =
false;
2546 for (
int i = 0; i < grid_divisions.x; i++) {
2547 for (
int j = 0; j < grid_divisions.y; j++) {
2548 for (
int k = 0; k < grid_divisions.z; k++) {
2549 int P_denom, P_trans;
2550 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
2552 found_intersections =
true;
2553 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
2554 DOCTEST_CHECK(r_bar > 0.0f);
2560 DOCTEST_CHECK(found_intersections);
2561 collision.clearVoxelData();
2565DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - GPU/CPU Consistency") {
2568 collision.disableMessages();
2570 vec3 grid_center(0, 0, 0);
2571 vec3 grid_size(6, 6, 6);
2572 int3 grid_divisions(3, 3, 3);
2575 std::vector<vec3> ray_origins;
2576 std::vector<vec3> ray_directions;
2578 for (
int i = 0; i < 5; i++) {
2579 ray_origins.push_back(
make_vec3(i - 2.0f, 0, -10));
2580 ray_directions.push_back(
make_vec3(0, 0, 1));
2584 collision.disableGPUAcceleration();
2585 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2588 std::vector<std::vector<std::vector<std::pair<int, float>>>> cpu_results(grid_divisions.x);
2589 for (
int i = 0; i < grid_divisions.x; i++) {
2590 cpu_results[i].resize(grid_divisions.y);
2591 for (
int j = 0; j < grid_divisions.y; j++) {
2592 cpu_results[i][j].resize(grid_divisions.z);
2593 for (
int k = 0; k < grid_divisions.z; k++) {
2594 int P_denom, P_trans;
2595 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
2596 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
2597 cpu_results[i][j][k] = std::make_pair(P_denom, r_bar);
2605 collision.enableGPUAcceleration();
2606 collision.clearVoxelData();
2607 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2611 for (
int i = 0; i < grid_divisions.x; i++) {
2612 for (
int j = 0; j < grid_divisions.y; j++) {
2613 for (
int k = 0; k < grid_divisions.z; k++) {
2614 int P_denom_gpu, P_trans_gpu;
2615 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom_gpu, P_trans_gpu);
2616 float r_bar_gpu = collision.getVoxelRbar(
make_int3(i, j, k));
2618 int P_denom_cpu = cpu_results[i][j][k].first;
2619 float r_bar_cpu = cpu_results[i][j][k].second;
2623 DOCTEST_CHECK(abs(P_denom_gpu - P_denom_cpu) <= 1);
2624 if (r_bar_cpu > 0 && r_bar_gpu > 0) {
2625 DOCTEST_CHECK(std::abs(r_bar_gpu - r_bar_cpu) < 1e-4f);
2632DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Parameter Validation") {
2635 collision.disableMessages();
2638 vec3 grid_center(0, 0, 0);
2639 vec3 negative_size(-5, 5, 5);
2640 int3 grid_divisions(2, 2, 2);
2641 std::vector<vec3> ray_origins = {
make_vec3(0, 0, -10)};
2642 std::vector<vec3> ray_directions = {
make_vec3(0, 0, 1)};
2646 collision.calculateVoxelRayPathLengths(grid_center, negative_size, grid_divisions, ray_origins, ray_directions);
2648 int P_denom, P_trans;
2649 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2650 DOCTEST_CHECK(P_denom >= 0);
2651 DOCTEST_CHECK(P_trans >= 0);
2652 }
catch (
const std::exception &e) {
2654 DOCTEST_CHECK(
true);
2658 int3 zero_divisions(0, 2, 2);
2660 collision.calculateVoxelRayPathLengths(grid_center,
make_vec3(5, 5, 5), zero_divisions, ray_origins, ray_directions);
2662 int P_denom_zero, P_trans_zero;
2664 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom_zero, P_trans_zero);
2666 DOCTEST_CHECK(P_denom_zero >= 0);
2667 }
catch (
const std::exception &inner_e) {
2669 DOCTEST_CHECK(
true);
2671 }
catch (
const std::exception &e) {
2672 DOCTEST_CHECK(
true);
2678 collision.calculateVoxelRayPathLengths(grid_center,
make_vec3(5, 5, 5),
make_int3(2, 2, 2), ray_origins, mismatched_directions);
2679 DOCTEST_CHECK(
false);
2680 }
catch (
const std::exception &e) {
2681 DOCTEST_CHECK(
true);
2685 vec3 valid_grid_size(4, 4, 4);
2686 int3 valid_divisions(2, 2, 2);
2687 collision.calculateVoxelRayPathLengths(grid_center, valid_grid_size, valid_divisions, ray_origins, ray_directions);
2690 collision.setVoxelTransmissionProbability(10, 15,
make_int3(0, 0, 0));
2691 int test_P_denom, test_P_trans;
2692 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), test_P_denom, test_P_trans);
2693 DOCTEST_CHECK(test_P_denom == 10);
2694 DOCTEST_CHECK(test_P_trans == 15);
2697 collision.setVoxelTransmissionProbability(-5, -3,
make_int3(0, 0, 0));
2698 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), test_P_denom, test_P_trans);
2699 DOCTEST_CHECK(test_P_denom == -5);
2700 DOCTEST_CHECK(test_P_trans == -3);
2703DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Mathematical Validation") {
2706 collision.disableMessages();
2709 vec3 grid_center(0, 0, 0);
2710 vec3 grid_size(2, 2, 2);
2711 int3 grid_divisions(1, 1, 1);
2714 std::vector<vec3> ray_origins = {
make_vec3(0, 0, -5)};
2715 std::vector<vec3> ray_directions = {
make_vec3(0, 0, 1)};
2717 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2719 float r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2722 DOCTEST_CHECK(std::abs(r_bar - 2.0f) < 0.1f);
2725 collision.clearVoxelData();
2726 std::vector<vec3> diagonal_origins = {
make_vec3(-2, -2, -2)};
2727 std::vector<vec3> diagonal_directions = {normalize(
make_vec3(1, 1, 1))};
2729 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, diagonal_origins, diagonal_directions);
2731 float diagonal_r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2734 float expected_diagonal = std::sqrt(3.0f) * 2.0f;
2735 DOCTEST_CHECK(std::abs(diagonal_r_bar - expected_diagonal) < 0.2f);
2738 collision.clearVoxelData();
2739 std::vector<vec3> multi_origins;
2740 std::vector<vec3> multi_directions;
2743 for (
int i = 0; i < 4; i++) {
2744 multi_origins.push_back(
make_vec3(0.5f * i - 0.75f, 0, -5));
2745 multi_directions.push_back(
make_vec3(0, 0, 1));
2748 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, multi_origins, multi_directions);
2750 int P_denom, P_trans;
2751 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2752 float multi_r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2755 DOCTEST_CHECK(P_denom == 4);
2757 DOCTEST_CHECK(P_trans == P_denom);
2759 DOCTEST_CHECK(std::abs(multi_r_bar - 2.0f) < 0.1f);
2762DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Numerical Precision") {
2765 collision.disableMessages();
2768 vec3 tiny_center(0, 0, 0);
2769 vec3 tiny_size(0.001f, 0.001f, 0.001f);
2770 int3 tiny_divisions(1, 1, 1);
2772 std::vector<vec3> tiny_origins = {
make_vec3(0, 0, -0.01f)};
2773 std::vector<vec3> tiny_directions = {
make_vec3(0, 0, 1)};
2775 collision.calculateVoxelRayPathLengths(tiny_center, tiny_size, tiny_divisions, tiny_origins, tiny_directions);
2776 float tiny_r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2779 DOCTEST_CHECK(tiny_r_bar > 0.0f);
2780 DOCTEST_CHECK(tiny_r_bar < 0.1f);
2783 vec3 large_center(0, 0, 0);
2784 vec3 large_size(1000.0f, 1000.0f, 1000.0f);
2785 int3 large_divisions(2, 2, 2);
2787 std::vector<vec3> large_origins = {
make_vec3(0, 0, -2000)};
2788 std::vector<vec3> large_directions = {
make_vec3(0, 0, 1)};
2790 collision.calculateVoxelRayPathLengths(large_center, large_size, large_divisions, large_origins, large_directions);
2791 float large_r_bar = collision.getVoxelRbar(
make_int3(1, 1, 1));
2794 DOCTEST_CHECK(large_r_bar > 0.0f);
2795 DOCTEST_CHECK(large_r_bar < 2000.0f);
2798 collision.clearVoxelData();
2799 vec3 precision_center(0, 0, 0);
2800 vec3 precision_size(10, 10, 10);
2801 int3 precision_divisions(5, 5, 5);
2803 std::vector<vec3> precision_origins;
2804 std::vector<vec3> precision_directions;
2805 for (
int i = 0; i < 10; i++) {
2806 precision_origins.push_back(
make_vec3(i - 5.0f, 0, -20));
2807 precision_directions.push_back(
make_vec3(0, 0, 1));
2811 collision.disableGPUAcceleration();
2812 collision.calculateVoxelRayPathLengths(precision_center, precision_size, precision_divisions, precision_origins, precision_directions);
2815 std::vector<float> cpu_rbars;
2816 for (
int i = 0; i < precision_divisions.x; i++) {
2817 for (
int j = 0; j < precision_divisions.y; j++) {
2818 for (
int k = 0; k < precision_divisions.z; k++) {
2819 cpu_rbars.push_back(collision.getVoxelRbar(
make_int3(i, j, k)));
2827 collision.enableGPUAcceleration();
2828 collision.clearVoxelData();
2829 collision.calculateVoxelRayPathLengths(precision_center, precision_size, precision_divisions, precision_origins, precision_directions);
2834 for (
int i = 0; i < precision_divisions.x; i++) {
2835 for (
int j = 0; j < precision_divisions.y; j++) {
2836 for (
int k = 0; k < precision_divisions.z; k++) {
2837 float gpu_rbar = collision.getVoxelRbar(
make_int3(i, j, k));
2838 float cpu_rbar = cpu_rbars[idx++];
2840 if (cpu_rbar > 0 && gpu_rbar > 0) {
2841 float relative_error = std::abs(gpu_rbar - cpu_rbar) / std::max(cpu_rbar, gpu_rbar);
2842 DOCTEST_CHECK(relative_error < 1e-5f);
2849DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Error Recovery and State Management") {
2852 collision.disableMessages();
2856 int P_denom, P_trans;
2857 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2859 DOCTEST_CHECK(P_denom == 0);
2860 DOCTEST_CHECK(P_trans == 0);
2861 }
catch (
const std::exception &e) {
2863 DOCTEST_CHECK(
true);
2867 float r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2869 DOCTEST_CHECK(r_bar == 0.0f);
2870 }
catch (
const std::exception &e) {
2872 DOCTEST_CHECK(
true);
2876 vec3 grid_center(0, 0, 0);
2877 vec3 grid_size(4, 4, 4);
2878 int3 grid_divisions(2, 2, 2);
2879 std::vector<vec3> ray_origins = {
make_vec3(0, 0, -10)};
2880 std::vector<vec3> ray_directions = {
make_vec3(0, 0, 1)};
2883 for (
int cycle = 0; cycle < 3; cycle++) {
2884 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2887 int P_denom, P_trans;
2888 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2889 DOCTEST_CHECK(P_denom >= 0);
2891 float r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2892 DOCTEST_CHECK(r_bar >= 0);
2895 collision.clearVoxelData();
2901 collision.setVoxelTransmissionProbability(10, 5,
make_int3(-1, 0, 0));
2902 DOCTEST_CHECK(
false);
2903 }
catch (
const std::exception &e) {
2905 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
2907 int P_denom, P_trans;
2908 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2909 DOCTEST_CHECK(P_denom >= 0);
2913DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Memory and Performance Stress") {
2916 collision.disableMessages();
2919 vec3 stress_center(0, 0, 0);
2920 vec3 stress_size(50, 50, 50);
2921 int3 stress_divisions(10, 10, 10);
2924 std::vector<vec3> stress_origins;
2925 std::vector<vec3> stress_directions;
2926 for (
int i = 0; i < 100; i++) {
2927 for (
int j = 0; j < 10; j++) {
2928 stress_origins.push_back(
make_vec3(i - 50.0f, j - 5.0f, -100));
2929 stress_directions.push_back(
make_vec3(0, 0, 1));
2934 auto start_time = std::chrono::high_resolution_clock::now();
2935 collision.calculateVoxelRayPathLengths(stress_center, stress_size, stress_divisions, stress_origins, stress_directions);
2936 auto end_time = std::chrono::high_resolution_clock::now();
2937 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
2940 bool found_data =
false;
2941 for (
int i = 0; i < stress_divisions.x && !found_data; i++) {
2942 for (
int j = 0; j < stress_divisions.y && !found_data; j++) {
2943 for (
int k = 0; k < stress_divisions.z && !found_data; k++) {
2944 int P_denom, P_trans;
2945 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
2948 DOCTEST_CHECK(P_trans <= P_denom);
2949 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
2950 DOCTEST_CHECK(r_bar > 0.0f);
2955 DOCTEST_CHECK(found_data);
2958 collision.clearVoxelData();
2961 int P_denom, P_trans;
2962 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2963 DOCTEST_CHECK(P_denom == 0);
2964 DOCTEST_CHECK(P_trans == 0);
2966 float r_bar = collision.getVoxelRbar(
make_int3(0, 0, 0));
2967 DOCTEST_CHECK(r_bar == 0.0f);
2970 for (
int cycle = 0; cycle < 5; cycle++) {
2971 vec3 cycle_size(8 + cycle * 2, 8 + cycle * 2, 8 + cycle * 2);
2972 int3 cycle_divisions(2 + cycle, 2 + cycle, 2 + cycle);
2974 std::vector<vec3> cycle_origins = {
make_vec3(0, 0, -20)};
2975 std::vector<vec3> cycle_directions = {
make_vec3(0, 0, 1)};
2977 collision.calculateVoxelRayPathLengths(stress_center, cycle_size, cycle_divisions, cycle_origins, cycle_directions);
2980 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
2981 DOCTEST_CHECK(P_denom >= 0);
2983 collision.clearVoxelData();
2987DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Integration with BVH") {
2990 collision.disableMessages();
2993 std::vector<uint> sphere_UUIDs =
context.addSphere(10,
make_vec3(0, 0, 0), 1.0f);
2996 collision.buildBVH(sphere_UUIDs);
2999 vec3 grid_center(0, 0, 0);
3000 vec3 grid_size(10, 10, 10);
3001 int3 grid_divisions(5, 5, 5);
3003 std::vector<vec3> ray_origins;
3004 std::vector<vec3> ray_directions;
3005 for (
int i = 0; i < 8; i++) {
3006 ray_origins.push_back(
make_vec3(i - 4.0f, 0, -15));
3007 ray_directions.push_back(
make_vec3(0, 0, 1));
3011 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
3014 bool found_voxel_data =
false;
3015 for (
int i = 0; i < grid_divisions.x; i++) {
3016 for (
int j = 0; j < grid_divisions.y; j++) {
3017 for (
int k = 0; k < grid_divisions.z; k++) {
3018 int P_denom, P_trans;
3019 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
3021 found_voxel_data =
true;
3022 DOCTEST_CHECK(P_trans >= 0);
3023 DOCTEST_CHECK(P_trans <= P_denom);
3025 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
3026 DOCTEST_CHECK(r_bar >= 0.0f);
3031 DOCTEST_CHECK(found_voxel_data);
3034 std::vector<uint> collision_results = collision.findCollisions(sphere_UUIDs[0]);
3035 DOCTEST_CHECK(collision_results.size() >= 0);
3038 collision.clearVoxelData();
3041 std::vector<uint> triangle_UUIDs;
3045 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
3048 std::vector<uint> new_collisions = collision.findCollisions(triangle_UUIDs);
3049 DOCTEST_CHECK(new_collisions.size() >= 0);
3052 int final_P_denom, final_P_trans;
3053 collision.getVoxelTransmissionProbability(
make_int3(2, 2, 2), final_P_denom, final_P_trans);
3054 DOCTEST_CHECK(final_P_denom >= 0);
3055 DOCTEST_CHECK(final_P_trans >= 0);
3058DOCTEST_TEST_CASE(
"CollisionDetection Voxel Ray Path Length - Edge Case Ray Geometries") {
3061 collision.disableMessages();
3063 vec3 grid_center(0, 0, 0);
3064 vec3 grid_size(4, 4, 4);
3065 int3 grid_divisions(2, 2, 2);
3068 std::vector<vec3> grazing_origins = {
make_vec3(-3, 2.0f, 0)};
3069 std::vector<vec3> grazing_directions = {
make_vec3(1, 0, 0)};
3071 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, grazing_origins, grazing_directions);
3074 bool found_grazing_intersection =
false;
3075 for (
int i = 0; i < grid_divisions.x; i++) {
3076 for (
int j = 0; j < grid_divisions.y; j++) {
3077 for (
int k = 0; k < grid_divisions.z; k++) {
3078 int P_denom, P_trans;
3079 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
3081 found_grazing_intersection =
true;
3082 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
3083 DOCTEST_CHECK(r_bar >= 0.0f);
3090 collision.clearVoxelData();
3091 std::vector<vec3> corner_origins = {
make_vec3(-3, -3, -3)};
3092 std::vector<vec3> corner_directions = {normalize(
make_vec3(1, 1, 1))};
3094 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, corner_origins, corner_directions);
3096 bool found_corner_intersection =
false;
3097 for (
int i = 0; i < grid_divisions.x; i++) {
3098 for (
int j = 0; j < grid_divisions.y; j++) {
3099 for (
int k = 0; k < grid_divisions.z; k++) {
3100 int P_denom, P_trans;
3101 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
3103 found_corner_intersection =
true;
3104 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
3105 DOCTEST_CHECK(r_bar >= 0.0f);
3112 collision.clearVoxelData();
3113 std::vector<vec3> near_zero_origins = {
make_vec3(0, 0, -5)};
3114 std::vector<vec3> near_zero_directions = {normalize(
make_vec3(1e-6f, 1e-6f, 1.0f))};
3116 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, near_zero_origins, near_zero_directions);
3119 bool found_near_zero_intersection =
false;
3120 for (
int i = 0; i < grid_divisions.x; i++) {
3121 for (
int j = 0; j < grid_divisions.y; j++) {
3122 for (
int k = 0; k < grid_divisions.z; k++) {
3123 int P_denom, P_trans;
3124 collision.getVoxelTransmissionProbability(
make_int3(i, j, k), P_denom, P_trans);
3126 found_near_zero_intersection =
true;
3127 float r_bar = collision.getVoxelRbar(
make_int3(i, j, k));
3128 DOCTEST_CHECK(r_bar >= 0.0f);
3129 DOCTEST_CHECK(std::isfinite(r_bar));
3138DOCTEST_TEST_CASE(
"CollisionDetection Generic Ray Casting - Basic Functionality") {
3141 collision.disableMessages();
3147 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
3153 CollisionDetection::CollisionDetection::HitResult result = collision.castRay(ray_origin, ray_direction);
3155 DOCTEST_CHECK(result.hit ==
true);
3156 DOCTEST_CHECK(result.primitive_UUID == triangle_uuid);
3157 DOCTEST_CHECK(result.distance > 0.9f);
3158 DOCTEST_CHECK(result.distance < 1.1f);
3161 DOCTEST_CHECK(result.intersection_point.x > 0.4f);
3162 DOCTEST_CHECK(result.intersection_point.x < 0.6f);
3163 DOCTEST_CHECK(std::abs(result.intersection_point.y) < 1e-5f);
3164 DOCTEST_CHECK(result.intersection_point.z > 0.4f);
3165 DOCTEST_CHECK(result.intersection_point.z < 0.6f);
3172 DOCTEST_CHECK(miss_result.
hit ==
false);
3173 DOCTEST_CHECK(miss_result.
distance < 0);
3178 float max_distance = 1.5f;
3181 DOCTEST_CHECK(limited_result.
hit ==
false);
3184DOCTEST_TEST_CASE(
"CollisionDetection Generic Ray Casting - CollisionDetection::RayQuery Structure") {
3187 collision.disableMessages();
3199 DOCTEST_CHECK(result1.
hit ==
true);
3205 DOCTEST_CHECK(result2.hit ==
true);
3206 DOCTEST_CHECK(result2.primitive_UUID == triangle2);
3212 DOCTEST_CHECK(result3.hit ==
true);
3213 DOCTEST_CHECK(result3.primitive_UUID == triangle1);
3219 DOCTEST_CHECK(result4.hit ==
false);
3222DOCTEST_TEST_CASE(
"CollisionDetection Batch Ray Casting") {
3225 collision.disableMessages();
3232 std::vector<CollisionDetection::RayQuery> queries;
3240 std::vector<CollisionDetection::HitResult> results = collision.castRays(queries, &stats);
3243 DOCTEST_CHECK(results.size() == 4);
3249 DOCTEST_CHECK(results[0].hit ==
true);
3250 DOCTEST_CHECK(results[1].hit ==
false);
3251 DOCTEST_CHECK(results[2].hit ==
true);
3252 DOCTEST_CHECK(results[3].hit ==
true);
3255 for (
const auto &result: results) {
3257 DOCTEST_CHECK(result.primitive_UUID == triangle);
3258 DOCTEST_CHECK(result.distance > 0);
3263DOCTEST_TEST_CASE(
"CollisionDetection SoA Batch Ray Casting Equivalence") {
3268 collision.disableMessages();
3274 std::vector<helios::vec3> origins = {
make_vec3(0, -1, 0),
make_vec3(2, -1, 0),
make_vec3(-0.5f, -1, 0),
make_vec3(0.5f, -1, 0),
make_vec3(0, -1, 0)};
3275 std::vector<helios::vec3> directions = {normalize(
make_vec3(0, 1, 0)), normalize(
make_vec3(0, 1, 0)), normalize(
make_vec3(0, 1, 0)), normalize(
make_vec3(0, 1, 0)), normalize(
make_vec3(0, 1, 0))};
3276 const size_t count = origins.size();
3277 const float max_distance = -1.0f;
3280 std::vector<CollisionDetection::RayQuery> queries;
3281 queries.reserve(count);
3282 for (
size_t i = 0; i < count; i++) {
3283 queries.emplace_back(origins[i], directions[i], max_distance);
3285 std::vector<CollisionDetection::HitResult> ref_results = collision.castRays(queries);
3288 constexpr uint MISS_UUID = 0xFFFFFFFFu;
3289 std::vector<float> soa_distance(count, -1.0f);
3290 std::vector<helios::vec3> soa_normal(count);
3291 std::vector<uint> soa_uuid(count, MISS_UUID);
3293 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA(origins.data(), directions.data(), count, max_distance, soa_distance.data(), soa_normal.data(), soa_uuid.data(), &soa_stats));
3297 size_t soa_hits = 0;
3298 for (
size_t i = 0; i < count; i++) {
3299 bool soa_hit = (soa_uuid[i] != MISS_UUID);
3300 DOCTEST_CHECK(soa_hit == ref_results[i].hit);
3303 DOCTEST_CHECK(soa_uuid[i] == ref_results[i].primitive_UUID);
3304 DOCTEST_CHECK(soa_distance[i] == doctest::Approx(ref_results[i].distance));
3305 DOCTEST_CHECK(soa_normal[i].x == doctest::Approx(ref_results[i].normal.x));
3306 DOCTEST_CHECK(soa_normal[i].y == doctest::Approx(ref_results[i].normal.y));
3307 DOCTEST_CHECK(soa_normal[i].z == doctest::Approx(ref_results[i].normal.z));
3310 DOCTEST_CHECK(soa_stats.
total_hits == soa_hits);
3313 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA(origins.data(), directions.data(), 0, max_distance, soa_distance.data(), soa_normal.data(), soa_uuid.data(),
nullptr));
3316 (void) sphere_first;
3319DOCTEST_TEST_CASE(
"CollisionDetection SoA Packet Ray Casting Equivalence") {
3326 collision.disableMessages();
3334 const size_t packet_size = 12;
3335 const std::vector<helios::vec3> packet_origins = {
make_vec3(0, 0, 6),
make_vec3(1.5f, -1.0f, 5),
make_vec3(-2.0f, 1.0f, 4),
make_vec3(0.2f, 0.2f, 8)};
3336 std::vector<helios::vec3> origins;
3337 std::vector<helios::vec3> directions;
3339 for (
size_t p = 0; p < packet_size; p++) {
3340 const float a = 0.02f * float(p);
3341 origins.push_back(po);
3342 directions.push_back(normalize(
make_vec3(std::sin(a), 0.5f * std::sin(a), -1.0f)));
3346 for (
size_t p = 0; p < packet_size; p++) {
3347 origins.push_back(
make_vec3(-0.4f + 0.06f *
float(p), 0.0f, 7.0f));
3348 directions.push_back(
make_vec3(0, 0, -1));
3350 const size_t count = origins.size();
3351 const float max_distance = -1.0f;
3353 constexpr uint MISS_UUID = 0xFFFFFFFFu;
3356 std::vector<float> ref_d(count, -1.0f);
3357 std::vector<helios::vec3> ref_n(count);
3358 std::vector<uint> ref_u(count, MISS_UUID);
3359 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA(origins.data(), directions.data(), count, max_distance, ref_d.data(), ref_n.data(), ref_u.data()));
3362 std::vector<float> pkt_d(count, -1.0f);
3363 std::vector<helios::vec3> pkt_n(count);
3364 std::vector<uint> pkt_u(count, MISS_UUID);
3366 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA_packets(origins.data(), directions.data(), count, packet_size, max_distance, pkt_d.data(), pkt_n.data(), pkt_u.data(), &pkt_stats));
3370 size_t pkt_hits = 0;
3371 for (
size_t i = 0; i < count; i++) {
3372 const bool ref_hit = (ref_u[i] != MISS_UUID);
3373 const bool pkt_hit = (pkt_u[i] != MISS_UUID);
3374 DOCTEST_CHECK(pkt_hit == ref_hit);
3375 if (ref_hit && pkt_hit) {
3382 DOCTEST_CHECK(pkt_d[i] == doctest::Approx(ref_d[i]));
3383 if (pkt_u[i] == ref_u[i]) {
3384 DOCTEST_CHECK(pkt_n[i].x == doctest::Approx(ref_n[i].x));
3385 DOCTEST_CHECK(pkt_n[i].y == doctest::Approx(ref_n[i].y));
3386 DOCTEST_CHECK(pkt_n[i].z == doctest::Approx(ref_n[i].z));
3389 DOCTEST_CHECK(pkt_d[i] == doctest::Approx(ref_d[i]).epsilon(1e-4));
3393 DOCTEST_CHECK(pkt_stats.
total_hits == pkt_hits);
3396 std::vector<float> p1_d(count, -1.0f);
3397 std::vector<helios::vec3> p1_n(count);
3398 std::vector<uint> p1_u(count, MISS_UUID);
3399 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA_packets(origins.data(), directions.data(), count, 1, max_distance, p1_d.data(), p1_n.data(), p1_u.data()));
3400 for (
size_t i = 0; i < count; i++) {
3401 DOCTEST_CHECK((p1_u[i] != MISS_UUID) == (ref_u[i] != MISS_UUID));
3402 if (ref_u[i] != MISS_UUID) {
3403 DOCTEST_CHECK(p1_u[i] == ref_u[i]);
3404 DOCTEST_CHECK(p1_d[i] == doctest::Approx(ref_d[i]));
3410 const size_t big = 600;
3411 std::vector<helios::vec3> bo(big), bd(big);
3412 for (
size_t i = 0; i < big; i++) {
3413 bo[i] =
make_vec3(-2.5f + 5.0f *
float(i) /
float(big - 1), 0.0f, 7.0f);
3416 std::vector<float> rd(big, -1.0f), kd(big, -1.0f);
3417 std::vector<helios::vec3> rn(big), kn(big);
3418 std::vector<uint> ru(big, MISS_UUID), ku(big, MISS_UUID);
3419 collision.castRaysSoA(bo.data(), bd.data(), big, max_distance, rd.data(), rn.data(), ru.data());
3420 collision.castRaysSoA_packets(bo.data(), bd.data(), big, big, max_distance, kd.data(), kn.data(), ku.data());
3421 for (
size_t i = 0; i < big; i++) {
3422 DOCTEST_CHECK((ku[i] != MISS_UUID) == (ru[i] != MISS_UUID));
3423 if (ru[i] != MISS_UUID) {
3425 DOCTEST_CHECK(kd[i] == doctest::Approx(rd[i]));
3431 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA_packets(origins.data(), directions.data(), 0, packet_size, max_distance, pkt_d.data(), pkt_n.data(), pkt_u.data(),
nullptr));
3434DOCTEST_TEST_CASE(
"CollisionDetection SoA Batch Ray Casting GPU/CPU Equivalence") {
3445 const int grid = 71;
3446 const float extent = 2.0f;
3447 const float c = extent / float(grid);
3448 std::vector<helios::vec3> centroids;
3449 centroids.reserve(
size_t(grid) *
size_t(grid));
3450 for (
int a = 0; a < grid; a++) {
3451 for (
int b = 0; b < grid; b++) {
3452 float by = -extent / 2.0f + a * c;
3453 float bz = -extent / 2.0f + b * c;
3454 vec3 v0(0, by + 0.2f * c, bz + 0.2f * c);
3455 vec3 v1(0, by + 0.8f * c, bz + 0.2f * c);
3456 vec3 v2(0, by + 0.5f * c, bz + 0.8f * c);
3457 context.addTriangle(v0, v1, v2);
3458 centroids.push_back(
make_vec3(0.0f, by + 0.5f * c, bz + 0.4f * c));
3461 const size_t num_tri = centroids.size();
3465 const int rays_per_dim = 1050;
3466 const size_t count = size_t(rays_per_dim) * size_t(rays_per_dim);
3467 std::vector<helios::vec3> origins(count), directions(count);
3468 for (
size_t idx = 0; idx < count; idx++) {
3470 bool make_miss = (idx % 53 == 0);
3471 origins[idx] =
make_vec3(-2.0f, make_miss ? target.
y + 100.0f : target.y, target.z);
3472 directions[idx] =
make_vec3(1.0f, 0.0f, 0.0f);
3474 const float max_distance = 10.0f;
3475 constexpr uint MISS_UUID = 0xFFFFFFFFu;
3478 collision.disableMessages();
3481 collision.disableGPUAcceleration();
3482 collision.buildBVH();
3483 std::vector<float> cpu_d(count, -1.0f);
3484 std::vector<helios::vec3> cpu_n(count);
3485 std::vector<uint> cpu_u(count, MISS_UUID);
3486 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA(origins.data(), directions.data(), count, max_distance, cpu_d.data(), cpu_n.data(), cpu_u.data()));
3489 collision.enableGPUAcceleration();
3490 collision.buildBVH();
3491 std::vector<float> gpu_d(count, -1.0f);
3492 std::vector<helios::vec3> gpu_n(count);
3493 std::vector<uint> gpu_u(count, MISS_UUID);
3495 DOCTEST_CHECK_NOTHROW(collision.castRaysSoA(origins.data(), directions.data(), count, max_distance, gpu_d.data(), gpu_n.data(), gpu_u.data(), &gpu_stats));
3505#ifdef HELIOS_CUDA_AVAILABLE
3506 if (collision.isGPUAccelerationEnabled()) {
3507 DOCTEST_MESSAGE(
"GPU path exercised (working CUDA device present)");
3509 DOCTEST_MESSAGE(
"SKIPPED GPU-path assertion: no usable CUDA device at runtime; comparison ran CPU-vs-CPU");
3514 size_t cpu_hits = 0, gpu_hits = 0, classify_mismatch = 0, uuid_mismatch = 0, dist_mismatch = 0, normal_mismatch = 0;
3515 for (
size_t i = 0; i < count; i++) {
3516 bool ch = (cpu_u[i] != MISS_UUID);
3517 bool gh = (gpu_u[i] != MISS_UUID);
3523 classify_mismatch++;
3527 if (gpu_u[i] != cpu_u[i])
3529 if (std::fabs(gpu_d[i] - cpu_d[i]) > 1e-3f)
3531 if ((gpu_n[i] - cpu_n[i]).magnitude() > 1e-3f)
3535 DOCTEST_CHECK(cpu_hits > 0);
3536 DOCTEST_CHECK(gpu_hits == cpu_hits);
3537 DOCTEST_CHECK(classify_mismatch == 0);
3538 DOCTEST_CHECK(uuid_mismatch == 0);
3539 DOCTEST_CHECK(dist_mismatch == 0);
3540 DOCTEST_CHECK(normal_mismatch == 0);
3543DOCTEST_TEST_CASE(
"CollisionDetection Grid Ray Intersection") {
3546 collision.disableMessages();
3557 std::vector<CollisionDetection::RayQuery> rays;
3562 auto grid_results = collision.performGridRayIntersection(grid_center, grid_size, grid_divisions, rays);
3564 DOCTEST_CHECK(grid_results.size() == 2);
3565 DOCTEST_CHECK(grid_results[0].size() == 2);
3566 DOCTEST_CHECK(grid_results[0][0].size() == 2);
3570 for (
int i = 0; i < 2; i++) {
3571 for (
int j = 0; j < 2; j++) {
3572 for (
int k = 0; k < 2; k++) {
3573 total_hits += grid_results[i][j][k].size();
3577 DOCTEST_CHECK(total_hits >= 1);
3580DOCTEST_TEST_CASE(
"CollisionDetection Ray Path Lengths Detailed") {
3583 collision.disableMessages();
3593 std::vector<vec3> ray_origins = {
3599 std::vector<CollisionDetection::HitResult> hit_results;
3600 collision.calculateRayPathLengthsDetailed(grid_center, grid_size, grid_divisions, ray_origins, ray_directions, hit_results);
3602 DOCTEST_CHECK(hit_results.size() == 3);
3605 DOCTEST_CHECK(hit_results[0].hit ==
true);
3606 DOCTEST_CHECK(hit_results[1].hit ==
true);
3607 DOCTEST_CHECK(hit_results[2].hit ==
false);
3610 DOCTEST_CHECK(hit_results[0].distance > 1.5f);
3611 DOCTEST_CHECK(hit_results[0].distance < 2.5f);
3612 DOCTEST_CHECK(hit_results[1].distance > 1.5f);
3613 DOCTEST_CHECK(hit_results[1].distance < 2.5f);
3616 int P_denom, P_trans;
3617 collision.getVoxelTransmissionProbability(
make_int3(0, 0, 0), P_denom, P_trans);
3618 DOCTEST_CHECK(P_denom >= 0);
3621DOCTEST_TEST_CASE(
"CollisionDetection Ray Casting - Normal Calculation") {
3624 collision.disableMessages();
3635 CollisionDetection::CollisionDetection::HitResult result = collision.castRay(ray_origin, ray_direction);
3637 DOCTEST_CHECK(result.hit ==
true);
3641 float dot_product = result.normal.x * expected_normal.
x + result.normal.y * expected_normal.
y + result.normal.z * expected_normal.
z;
3642 DOCTEST_CHECK(std::abs(dot_product) > 0.9f);
3652 DOCTEST_CHECK(patch_result.
hit ==
true);
3660DOCTEST_TEST_CASE(
"CollisionDetection Ray Casting - Edge Cases and Error Handling") {
3663 collision.disableMessages();
3671 DOCTEST_CHECK(zero_result.
hit ==
false);
3680 DOCTEST_CHECK(inf_result.
hit ==
true);
3684 DOCTEST_CHECK(neg_result.
hit ==
true);
3687 std::vector<uint> empty_targets;
3689 DOCTEST_CHECK(empty_result.
hit ==
true);
3692 std::vector<uint> invalid_targets = {99999, triangle, 88888};
3694 DOCTEST_CHECK(invalid_result.
hit ==
true);
3698DOCTEST_TEST_CASE(
"CollisionDetection Ray Casting - Performance and Scalability") {
3701 collision.disableMessages();
3704 std::vector<uint> triangles;
3705 for (
int i = 0; i < 100; i++) {
3708 triangles.push_back(triangle);
3712 std::vector<CollisionDetection::RayQuery> many_rays;
3713 for (
int i = 0; i < 200; i++) {
3714 float x = (i % 100) * 0.1f + 0.025f;
3718 auto start_time = std::chrono::high_resolution_clock::now();
3721 std::vector<CollisionDetection::HitResult> results = collision.castRays(many_rays, &stats);
3723 auto end_time = std::chrono::high_resolution_clock::now();
3724 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
3726 DOCTEST_CHECK(results.size() == 200);
3731 DOCTEST_CHECK(duration.count() < 1000);
3738DOCTEST_TEST_CASE(
"CollisionDetection Ray Casting - Integration with Existing BVH") {
3741 collision.disableMessages();
3748 collision.buildBVH();
3751 DOCTEST_CHECK(result1.
hit ==
true);
3755 DOCTEST_CHECK(result2.
hit ==
true);
3759 collision.enableAutomaticBVHRebuilds();
3766 DOCTEST_CHECK(result3.
hit ==
true);
3770DOCTEST_TEST_CASE(
"CollisionDetection Ray Casting - Compatibility with Other Plugin Methods") {
3773 collision.disableMessages();
3779 auto collisions = collision.findCollisions(triangles[0]);
3780 DOCTEST_CHECK(collisions.size() > 0);
3783 DOCTEST_CHECK(triangles.size() == 5);
3786 auto cone_result = collision.findOptimalConePath(
make_vec3(0, -2, 0),
make_vec3(0, 1, 0), M_PI / 6, 3.0f);
3790 DOCTEST_CHECK(collisions.size() > 0);
3791 DOCTEST_CHECK(
true);
3798DOCTEST_TEST_CASE(
"CollisionDetection - BVH Optimization Mode Management") {
3801 collision.disableMessages();
3815 collision.buildBVH();
3822 auto memory_stats = collision.getBVHMemoryUsage();
3823 DOCTEST_CHECK(memory_stats.soa_memory_bytes > 0);
3826 DOCTEST_CHECK(memory_stats.quantized_memory_bytes == 0);
3827 DOCTEST_CHECK(memory_stats.quantized_reduction_percent == 0.0f);
3830DOCTEST_TEST_CASE(
"CollisionDetection - Optimized Ray Casting Correctness") {
3833 collision.disableMessages();
3840 std::vector<CollisionDetection::RayQuery> rays;
3846 std::vector<CollisionDetection::HitResult> legacy_results, soa_results;
3850 legacy_results = collision.castRays(rays);
3854 soa_results = collision.castRaysOptimized(rays);
3857 DOCTEST_REQUIRE(legacy_results.size() == 3);
3858 DOCTEST_REQUIRE(soa_results.size() == 3);
3860 for (
size_t i = 0; i < legacy_results.size(); i++) {
3862 DOCTEST_CHECK(legacy_results[i].hit == soa_results[i].hit);
3864 if (legacy_results[i].hit) {
3866 DOCTEST_CHECK(legacy_results[i].primitive_UUID == soa_results[i].primitive_UUID);
3869 DOCTEST_CHECK(std::abs(legacy_results[i].distance - soa_results[i].distance) < 0.001f);
3874 DOCTEST_CHECK(legacy_results[0].hit ==
true);
3875 DOCTEST_CHECK(legacy_results[1].hit ==
true);
3876 DOCTEST_CHECK(legacy_results[2].hit ==
false);
3879DOCTEST_TEST_CASE(
"CollisionDetection - Ray Streaming Interface") {
3882 collision.disableMessages();
3886 for (
int i = 0; i < 5; i++) {
3893 std::vector<CollisionDetection::RayQuery> batch;
3896 for (
int i = 0; i < 50; i++) {
3897 float x = (i % 5) * 2.0f + 0.5f;
3903 DOCTEST_CHECK(stream.
packets.size() > 0);
3907 bool success = collision.processRayStream(stream, &stats);
3908 DOCTEST_CHECK(success ==
true);
3913 DOCTEST_CHECK(results.size() == 50);
3916 size_t hit_count = 0;
3917 for (
const auto &result: results) {
3921 DOCTEST_CHECK(hit_count > 40);
3924DOCTEST_TEST_CASE(
"CollisionDetection - BVH Layout Conversion Methods") {
3927 collision.disableMessages();
3936 collision.buildBVH();
3939 std::vector<CollisionDetection::RayQuery> test_rays = {
3946 auto legacy_results = collision.castRays(test_rays);
3947 auto soa_results = collision.castRaysOptimized(test_rays);
3948 auto memory_stats = collision.getBVHMemoryUsage();
3951 DOCTEST_REQUIRE(legacy_results.size() == 3);
3952 DOCTEST_REQUIRE(soa_results.size() == 3);
3955 size_t legacy_hits = 0, soa_hits = 0;
3956 for (
size_t i = 0; i < 3; i++) {
3957 if (legacy_results[i].hit)
3959 if (soa_results[i].hit)
3964 DOCTEST_CHECK(legacy_hits == soa_hits);
3967 DOCTEST_CHECK(memory_stats.soa_memory_bytes > 0);
3968 DOCTEST_CHECK(memory_stats.quantized_memory_bytes == 0);
3969 DOCTEST_CHECK(memory_stats.quantized_reduction_percent == 0.0f);
3972DOCTEST_TEST_CASE(
"CollisionDetection - RayPacket Edge Cases and Functionality") {
3975 collision.disableMessages();
3979 DOCTEST_CHECK(empty_packet.
ray_count == 0);
3984 DOCTEST_CHECK_NOTHROW(empty_packet.
clear());
3991 std::vector<CollisionDetection::RayQuery> test_queries;
3992 for (
int i = 0; i < 150; i++) {
3995 test_queries.push_back(query);
3996 capacity_packet.
addRay(query);
3999 DOCTEST_CHECK(capacity_packet.
ray_count == 150);
4000 DOCTEST_CHECK(capacity_packet.
origins.size() == 150);
4001 DOCTEST_CHECK(capacity_packet.
directions.size() == 150);
4002 DOCTEST_CHECK(capacity_packet.
results.size() == 150);
4005 auto converted_queries = capacity_packet.
toRayQueries();
4006 DOCTEST_REQUIRE(converted_queries.size() == 150);
4008 for (
size_t i = 0; i < 150; i++) {
4009 DOCTEST_CHECK(converted_queries[i].origin.
magnitude() == test_queries[i].origin.magnitude());
4010 DOCTEST_CHECK(converted_queries[i].direction.magnitude() == test_queries[i].direction.magnitude());
4011 DOCTEST_CHECK(converted_queries[i].max_distance == test_queries[i].max_distance);
4015 size_t expected_memory = (150 * 2) *
sizeof(
helios::vec3) +
4016 150 *
sizeof(float) +
4019 DOCTEST_CHECK(actual_memory >= expected_memory);
4022 capacity_packet.
clear();
4023 DOCTEST_CHECK(capacity_packet.
ray_count == 0);
4024 DOCTEST_CHECK(capacity_packet.
origins.empty());
4025 DOCTEST_CHECK(capacity_packet.
directions.empty());
4026 DOCTEST_CHECK(capacity_packet.
results.empty());
4030DOCTEST_TEST_CASE(
"CollisionDetection - RayStream Batch Management") {
4033 collision.disableMessages();
4037 for (
int i = 0; i < 3; i++) {
4044 std::vector<CollisionDetection::RayQuery> large_batch;
4048 for (
size_t i = 0; i < total_rays; i++) {
4049 float x = (i % 3) * 3.0f + 0.5f;
4053 large_stream.
addRays(large_batch);
4054 DOCTEST_CHECK(large_stream.
total_rays == total_rays);
4055 DOCTEST_CHECK(large_stream.
packets.size() == 3);
4059 DOCTEST_CHECK(stream_memory_before > 0);
4062 bool large_success = collision.processRayStream(large_stream, &large_stats);
4063 DOCTEST_CHECK(large_success ==
true);
4068 DOCTEST_CHECK(all_results.size() == total_rays);
4071 size_t hit_count = 0;
4072 for (
const auto &result: all_results) {
4076 float hit_rate = float(hit_count) / float(total_rays);
4077 DOCTEST_CHECK(hit_rate > 0.8f);
4082 DOCTEST_CHECK(empty_stream.
packets.empty());
4086 bool empty_success = collision.processRayStream(empty_stream, &empty_stats);
4087 DOCTEST_CHECK(empty_success ==
true);
4091 large_stream.
clear();
4093 DOCTEST_CHECK(large_stream.
packets.empty());
4098DOCTEST_TEST_CASE(
"CollisionDetection - SoA Precision Validation") {
4101 collision.disableMessages();
4110 collision.buildBVH();
4112 std::vector<CollisionDetection::RayQuery> precision_test_rays = {
4119 CollisionDetection::RayQuery(
make_vec3(20, -1, 0),
make_vec3(0, 1, 0)),
CollisionDetection::RayQuery(
make_vec3(-5, -1, 0),
make_vec3(0, 1, 0))};
4121 auto legacy_results = collision.castRays(precision_test_rays);
4122 auto soa_results = collision.castRaysOptimized(precision_test_rays);
4124 DOCTEST_REQUIRE(legacy_results.size() == precision_test_rays.size());
4125 DOCTEST_REQUIRE(soa_results.size() == precision_test_rays.size());
4128 for (
size_t i = 0; i < precision_test_rays.size(); i++) {
4129 DOCTEST_CHECK(legacy_results[i].hit == soa_results[i].hit);
4131 if (legacy_results[i].hit && soa_results[i].hit) {
4132 DOCTEST_CHECK(legacy_results[i].primitive_UUID == soa_results[i].primitive_UUID);
4133 DOCTEST_CHECK(std::abs(legacy_results[i].distance - soa_results[i].distance) < 0.001f);
4138 auto memory_stats = collision.getBVHMemoryUsage();
4139 DOCTEST_CHECK(memory_stats.soa_memory_bytes > 0);
4140 DOCTEST_CHECK(memory_stats.quantized_memory_bytes == 0);
4141 DOCTEST_CHECK(memory_stats.quantized_reduction_percent == 0.0f);
4144DOCTEST_TEST_CASE(
"CollisionDetection - Error Handling and Edge Cases") {
4147 collision.disableMessages();
4155 auto initial_mode = collision.getBVHOptimizationMode();
4156 DOCTEST_CHECK_NOTHROW(collision.setBVHOptimizationMode(initial_mode));
4157 DOCTEST_CHECK(collision.getBVHOptimizationMode() == initial_mode);
4160 auto empty_memory_stats = collision.getBVHMemoryUsage();
4161 DOCTEST_CHECK(empty_memory_stats.soa_memory_bytes == 0);
4162 DOCTEST_CHECK(empty_memory_stats.quantized_memory_bytes == 0);
4167 DOCTEST_CHECK_NOTHROW(collision.castRays(empty_test_rays));
4168 DOCTEST_CHECK_NOTHROW(collision.castRaysOptimized(empty_test_rays));
4170 auto empty_results = collision.castRaysOptimized(empty_test_rays);
4171 DOCTEST_CHECK(empty_results.size() == 1);
4172 DOCTEST_CHECK(empty_results[0].hit ==
false);
4177 DOCTEST_CHECK_NOTHROW(collision.processRayStream(empty_stream, &empty_stats));
4181 collision.buildBVH();
4185 auto recovery_results = collision.castRaysOptimized(recovery_rays);
4186 DOCTEST_CHECK(recovery_results.size() == 1);
4187 DOCTEST_CHECK(recovery_results[0].hit ==
true);
4190DOCTEST_TEST_CASE(
"CollisionDetection - Memory and Statistics Validation") {
4193 collision.disableMessages();
4196 for (
int i = 0; i < 8; i++) {
4198 float y = (i % 2) * 2.0f;
4205 collision.buildBVH();
4206 auto legacy_memory = collision.getBVHMemoryUsage();
4209 auto memory_stats = collision.getBVHMemoryUsage();
4212 DOCTEST_CHECK(memory_stats.soa_memory_bytes > 0);
4213 DOCTEST_CHECK(memory_stats.quantized_memory_bytes == 0);
4214 DOCTEST_CHECK(memory_stats.quantized_reduction_percent == 0.0f);
4217 std::vector<CollisionDetection::RayQuery> stat_test_rays;
4218 for (
int i = 0; i < 20; i++) {
4219 float x = (i % 4) * 2.0f + 0.5f;
4220 float y = (i / 4) * 2.0f + 0.5f;
4225 auto stat_results = collision.castRaysOptimized(stat_test_rays, &stats);
4229 DOCTEST_CHECK(stat_results.size() == 20);
4239 stats_stream.
addRays(stat_test_rays);
4242 bool stream_success = collision.processRayStream(stats_stream, &stream_stats);
4243 DOCTEST_CHECK(stream_success ==
true);
4255DOCTEST_TEST_CASE(
"CollisionDetection Voxel Primitive Intersection - Basic Ray-AABB Tests") {
4258 collision.disableMessages();
4259 collision.disableGPUAcceleration();
4264 collision.buildBVH();
4271 ray.max_distance = 10.0f;
4273 auto results = collision.castRays({ray});
4274 DOCTEST_CHECK(results.size() == 1);
4275 DOCTEST_CHECK(results[0].hit ==
true);
4276 DOCTEST_CHECK(results[0].distance > 3.9f);
4277 DOCTEST_CHECK(results[0].distance < 4.1f);
4278 DOCTEST_CHECK(results[0].primitive_UUID == voxel_uuid);
4286 ray.max_distance = 10.0f;
4288 auto results = collision.castRays({ray});
4289 DOCTEST_CHECK(results.size() == 1);
4290 DOCTEST_CHECK(results[0].hit ==
false);
4298 ray.max_distance = 10.0f;
4300 auto results = collision.castRays({ray});
4301 DOCTEST_CHECK(results.size() == 1);
4302 DOCTEST_CHECK(results[0].hit ==
true);
4303 DOCTEST_CHECK(results[0].distance > 0.9f);
4304 DOCTEST_CHECK(results[0].distance < 1.1f);
4308DOCTEST_TEST_CASE(
"CollisionDetection Voxel Primitive Intersection - Multiple Voxels") {
4311 collision.disableMessages();
4312 collision.disableGPUAcceleration();
4319 collision.buildBVH();
4326 ray.max_distance = 10.0f;
4328 auto results = collision.castRays({ray});
4329 DOCTEST_CHECK(results.size() == 1);
4330 DOCTEST_CHECK(results[0].hit ==
true);
4331 DOCTEST_CHECK(results[0].primitive_UUID == voxel1);
4339 ray.max_distance = 20.0f;
4341 auto results = collision.castRays({ray});
4342 DOCTEST_CHECK(results.size() == 1);
4343 DOCTEST_CHECK(results[0].hit ==
true);
4344 DOCTEST_CHECK(results[0].primitive_UUID == voxel1);
4345 DOCTEST_CHECK(results[0].distance > 2.9f);
4346 DOCTEST_CHECK(results[0].distance < 3.1f);
4350DOCTEST_TEST_CASE(
"CollisionDetection Voxel Primitive Intersection - GPU vs CPU Consistency") {
4353 collision.disableMessages();
4356 std::vector<uint> voxel_uuids;
4362 std::vector<CollisionDetection::RayQuery> test_rays;
4365 for (
int i = 0; i < 5; i++) {
4366 for (
int j = 0; j < 3; j++) {
4368 ray.origin =
make_vec3(i * 2.0f - 4.0f, j * 2.0f - 2.0f, -8.0f);
4369 ray.direction = normalize(
make_vec3(0.1f * i, 0.1f * j, 1.0f));
4370 ray.max_distance = 20.0f;
4371 test_rays.push_back(ray);
4375 collision.buildBVH();
4378 collision.disableGPUAcceleration();
4379 auto cpu_results = collision.castRays(test_rays);
4382 std::vector<CollisionDetection::HitResult> gpu_results;
4385 collision.enableGPUAcceleration();
4386 gpu_results = collision.castRays(test_rays);
4390 DOCTEST_CHECK(cpu_results.size() == gpu_results.size());
4391 DOCTEST_CHECK(cpu_results.size() == test_rays.size());
4393 for (
size_t i = 0; i < cpu_results.size(); i++) {
4394 DOCTEST_CHECK(cpu_results[i].hit == gpu_results[i].hit);
4396 if (cpu_results[i].hit && gpu_results[i].hit) {
4398 DOCTEST_CHECK(std::abs(cpu_results[i].distance - gpu_results[i].distance) < 0.01f);
4399 DOCTEST_CHECK(cpu_results[i].primitive_UUID == gpu_results[i].primitive_UUID);
4406DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Ray-Triangle Intersection Algorithms") {
4409 collision.disableMessages();
4410 collision.disableGPUAcceleration();
4416 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
4419 vec3 ray_origin =
make_vec3(1.0f / 3.0f, 1.0f / 3.0f, -1.0f);
4424 DOCTEST_CHECK(result.
hit ==
true);
4428 DOCTEST_CHECK(std::abs(result.
distance - 1.0f) < 1e-6f);
4436 vec3 expected_normal = normalize(
cross(v1 - v0, v2 - v0));
4437 float normal_dot = result.
normal.
x * expected_normal.
x + result.
normal.
y * expected_normal.
y + result.
normal.
z * expected_normal.
z;
4438 DOCTEST_CHECK(std::abs(normal_dot - 1.0f) < 1e-6f);
4441DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Edge Case Intersections") {
4444 collision.disableMessages();
4445 collision.disableGPUAcceleration();
4451 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
4459 DOCTEST_CHECK(result.
hit ==
true);
4471 if (vertex_result.
hit) {
4478DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Barycentric Coordinate Validation") {
4481 collision.disableMessages();
4482 collision.disableGPUAcceleration();
4485 float sqrt3 = std::sqrt(3.0f);
4489 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
4492 vec3 centroid = (v0 + v1 + v2) * (1.0f / 3.0f);
4498 DOCTEST_CHECK(result.
hit ==
true);
4503 vec3 midpoint_v0_v1 = (v0 + v1) * 0.5f;
4504 vec3 midpoint_ray_origin =
make_vec3(midpoint_v0_v1.
x, midpoint_v0_v1.
y, -1);
4508 DOCTEST_CHECK(midpoint_result.
hit ==
true);
4515DOCTEST_TEST_CASE(
"CollisionDetection GPU-Specific - Direct castRaysGPU Testing") {
4518 collision.disableMessages();
4521 std::vector<uint> uuids;
4527 std::vector<CollisionDetection::RayQuery> queries;
4528 for (
int i = 0; i < 100; i++) {
4533 queries.push_back(query);
4538 collision.enableGPUAcceleration();
4539#ifdef HELIOS_CUDA_AVAILABLE
4540 if (collision.isGPUAccelerationEnabled()) {
4542 std::vector<CollisionDetection::HitResult> gpu_results = collision.castRaysGPU(queries, gpu_stats);
4544 DOCTEST_CHECK(gpu_results.size() == queries.size());
4548 collision.disableGPUAcceleration();
4550 std::vector<CollisionDetection::HitResult> cpu_results = collision.castRays(queries, &cpu_stats);
4553 DOCTEST_CHECK(cpu_results.size() == gpu_results.size());
4555 int consistent_hits = 0;
4556 for (
size_t i = 0; i < cpu_results.size(); i++) {
4557 if (cpu_results[i].hit == gpu_results[i].hit) {
4559 if (cpu_results[i].hit) {
4561 DOCTEST_CHECK(std::abs(cpu_results[i].distance - gpu_results[i].distance) < 0.001f);
4562 DOCTEST_CHECK(cpu_results[i].primitive_UUID == gpu_results[i].primitive_UUID);
4568 DOCTEST_CHECK(consistent_hits >= (
int) (0.95f * queries.size()));
4571 DOCTEST_WARN(
"GPU acceleration not available - skipping direct GPU tests");
4574 }
catch (std::exception &e) {
4575 DOCTEST_WARN((std::string(
"GPU test failed (expected on non-NVIDIA systems): ") + e.what()).c_str());
4579DOCTEST_TEST_CASE(
"CollisionDetection GPU-Specific - Error Handling and Edge Cases") {
4582 collision.disableMessages();
4585 collision.enableGPUAcceleration();
4586#ifdef HELIOS_CUDA_AVAILABLE
4587 if (collision.isGPUAccelerationEnabled()) {
4590 std::vector<CollisionDetection::RayQuery> empty_queries;
4592 std::vector<CollisionDetection::HitResult> results = collision.castRaysGPU(empty_queries, stats);
4593 DOCTEST_CHECK(results.empty());
4597 std::vector<CollisionDetection::RayQuery> large_batch;
4598 for (
int i = 0; i < 10000; i++) {
4603 large_batch.push_back(query);
4607 std::vector<CollisionDetection::HitResult> large_results = collision.castRaysGPU(large_batch, large_stats);
4608 DOCTEST_CHECK(large_results.size() == large_batch.size());
4612 std::vector<CollisionDetection::RayQuery> degenerate_queries;
4616 degenerate_queries.push_back(degenerate);
4619 std::vector<CollisionDetection::HitResult> degenerate_results = collision.castRaysGPU(degenerate_queries, degenerate_stats);
4620 DOCTEST_CHECK(degenerate_results.size() == 1);
4621 DOCTEST_CHECK(degenerate_results[0].hit ==
false);
4624 DOCTEST_WARN(
"GPU acceleration not available - skipping GPU error handling tests");
4627 }
catch (std::exception &e) {
4628 DOCTEST_WARN((std::string(
"GPU error handling test failed: ") + e.what()).c_str());
4634DOCTEST_TEST_CASE(
"CollisionDetection Floating-Point Precision - Extreme Values") {
4637 collision.disableMessages();
4638 collision.disableGPUAcceleration();
4641 float epsilon = 1e-6f;
4645 uint small_triangle =
context.addTriangle(v0_small, v1_small, v2_small);
4647 vec3 ray_origin =
make_vec3(epsilon / 3.0f, epsilon / 3.0f, -epsilon);
4652 DOCTEST_CHECK(std::isfinite(small_result.
distance));
4658 float large_scale = 1e6f;
4662 uint large_triangle =
context.addTriangle(v0_large, v1_large, v2_large);
4668 DOCTEST_CHECK(std::isfinite(large_result.
distance));
4669 if (large_result.
hit) {
4676DOCTEST_TEST_CASE(
"CollisionDetection Floating-Point Precision - Near-Parallel Rays") {
4679 collision.disableMessages();
4680 collision.disableGPUAcceleration();
4686 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
4689 float tiny_angle = 1e-6f;
4691 vec3 near_parallel_direction = normalize(
make_vec3(0, tiny_angle, 1));
4696 DOCTEST_CHECK(std::isfinite(near_parallel_result.
distance));
4697 if (near_parallel_result.
hit) {
4701 DOCTEST_CHECK(near_parallel_result.
distance > 0);
4705DOCTEST_TEST_CASE(
"CollisionDetection Floating-Point Precision - Boundary Conditions") {
4708 collision.disableMessages();
4709 collision.disableGPUAcceleration();
4715 uint triangle_uuid =
context.addTriangle(v0, v1, v2);
4718 float boundary_offset = 1e-8f;
4720 std::vector<vec3> boundary_origins = {
4722 make_vec3(1 + boundary_offset, 0.5f, -1),
4724 make_vec3(0.5f + boundary_offset, 0.5f + boundary_offset, -1)
4727 for (
const auto &origin: boundary_origins) {
4732 DOCTEST_CHECK(std::isfinite(result.
distance));
4743DOCTEST_TEST_CASE(
"CollisionDetection Complex Geometry - Multi-Primitive Accuracy") {
4746 collision.disableMessages();
4747 collision.disableGPUAcceleration();
4750 std::vector<uint> uuids;
4753 for (
int i = 0; i < 5; i++) {
4754 for (
int j = 0; j < 5; j++) {
4758 uuids.push_back(uuid);
4763 int correct_predictions = 0;
4764 int total_predictions = 0;
4766 for (
int i = 0; i < 10; i++) {
4767 for (
int j = 0; j < 10; j++) {
4777 bool should_hit =
false;
4778 for (
int ti = 0; ti < 5; ti++) {
4779 for (
int tj = 0; tj < 5; tj++) {
4780 float tx = ti * 0.8f;
4781 float ty = tj * 0.8f;
4794 float dot00 = v0.
x * v0.
x + v0.
y * v0.
y + v0.
z * v0.
z;
4795 float dot01 = v0.
x * v1.
x + v0.
y * v1.
y + v0.
z * v1.
z;
4796 float dot02 = v0.
x * v2.
x + v0.
y * v2.
y + v0.
z * v2.
z;
4797 float dot11 = v1.
x * v1.
x + v1.
y * v1.
y + v1.
z * v1.
z;
4798 float dot12 = v1.
x * v2.
x + v1.
y * v2.
y + v1.
z * v2.
z;
4800 float inv_denom = 1.0f / (dot00 * dot11 - dot01 * dot01);
4801 float u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
4802 float v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
4805 const float EPSILON = 1e-6f;
4806 if ((u >= -EPSILON) && (v >= -EPSILON) && (u + v <= 1 + EPSILON)) {
4815 if (result.
hit == should_hit) {
4816 correct_predictions++;
4818 total_predictions++;
4823 float accuracy = (float) correct_predictions / (
float) total_predictions;
4824 DOCTEST_CHECK(accuracy == 1.0f);
4827DOCTEST_TEST_CASE(
"CollisionDetection Complex Geometry - Stress Test Validation") {
4830 collision.disableMessages();
4831 collision.disableGPUAcceleration();
4834 std::vector<uint> stress_uuids;
4835 for (
int i = 0; i < 1000; i++) {
4836 float x = (rand() % 100) * 0.01f;
4837 float y = (rand() % 100) * 0.01f;
4838 float z = (rand() % 10) * 0.01f;
4839 float size = 0.1f + (rand() % 10) * 0.01f;
4842 stress_uuids.push_back(uuid);
4846 std::vector<CollisionDetection::RayQuery> stress_queries;
4847 for (
int i = 0; i < 500; i++) {
4849 query.
origin =
make_vec3((rand() % 200) * 0.01f - 1.0f, (rand() % 200) * 0.01f - 1.0f, -1.0f);
4852 stress_queries.push_back(query);
4856 std::vector<CollisionDetection::HitResult> stress_results = collision.castRays(stress_queries, &stats);
4858 DOCTEST_CHECK(stress_results.size() == stress_queries.size());
4862 int valid_results = 0;
4863 for (
const auto &result: stress_results) {
4869 DOCTEST_CHECK(valid_results == (
int) stress_results.size());
4874DOCTEST_TEST_CASE(
"CollisionDetection Performance Regression - BVH Construction Timing") {
4877 collision.disableMessages();
4880 std::vector<uint> large_geometry;
4881 for (
int i = 0; i < 5000; i++) {
4884 large_geometry.push_back(uuid);
4888 auto start_time = std::chrono::high_resolution_clock::now();
4889 collision.buildBVH();
4890 auto end_time = std::chrono::high_resolution_clock::now();
4892 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4895 DOCTEST_CHECK(duration.count() < 5000);
4898 DOCTEST_CHECK(collision.isBVHValid() ==
true);
4899 DOCTEST_CHECK(collision.getPrimitiveCount() == large_geometry.size());
4901 size_t node_count, leaf_count, max_depth;
4902 collision.getBVHStatistics(node_count, leaf_count, max_depth);
4905 DOCTEST_CHECK(node_count > 0);
4906 DOCTEST_CHECK(leaf_count > 0);
4907 DOCTEST_CHECK(max_depth > 0);
4908 DOCTEST_CHECK(max_depth < 50);
4911DOCTEST_TEST_CASE(
"CollisionDetection Performance Regression - Ray Casting Throughput") {
4914 collision.disableMessages();
4915 collision.disableGPUAcceleration();
4918 for (
int i = 0; i < 1000; i++) {
4919 float x = (i % 50) * 0.2f;
4920 float y = (i / 50) * 0.2f;
4925 std::vector<CollisionDetection::RayQuery> throughput_queries;
4926 for (
int i = 0; i < 10000; i++) {
4930 throughput_queries.push_back(query);
4934 auto start_time = std::chrono::high_resolution_clock::now();
4936 std::vector<CollisionDetection::HitResult> results = collision.castRays(throughput_queries, &stats);
4937 auto end_time = std::chrono::high_resolution_clock::now();
4939 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
4942 DOCTEST_CHECK(duration.count() < 2000);
4945 DOCTEST_CHECK(results.size() == throughput_queries.size());
4949 float rays_per_second = (float) throughput_queries.size() / (duration.count() / 1000.0f);
4950 DOCTEST_CHECK(rays_per_second > 1000.0f);
4953DOCTEST_TEST_CASE(
"CollisionDetection Performance Regression - Memory Usage Validation") {
4956 collision.disableMessages();
4959 for (
int i = 0; i < 2000; i++) {
4960 float x = i * 0.05f;
4966 collision.buildBVH();
4968 auto memory_stats = collision.getBVHMemoryUsage();
4971 DOCTEST_CHECK(memory_stats.soa_memory_bytes < 100 * 1024 * 1024);
4972 DOCTEST_CHECK(memory_stats.soa_memory_bytes > 0);
4976 std::vector<CollisionDetection::RayQuery> stream_queries;
4978 for (
int i = 0; i < 5000; i++) {
4982 stream_queries.push_back(query);
4985 stream.
addRays(stream_queries);
4989 DOCTEST_CHECK(stream_memory < 50 * 1024 * 1024);
4990 DOCTEST_CHECK(stream_memory > 0);
4995DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Ray-Patch Intersection") {
4998 collision.disableMessages();
4999 collision.disableGPUAcceleration();
5014 DOCTEST_CHECK(result.
hit ==
true);
5018 DOCTEST_CHECK(std::abs(result.
distance - 1.0f) < 1e-6f);
5026DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Ray-Patch Edge Cases") {
5029 collision.disableMessages();
5030 collision.disableGPUAcceleration();
5041 DOCTEST_CHECK(edge_result.
hit ==
true);
5052 DOCTEST_CHECK(corner_result.
hit ==
true);
5058DOCTEST_TEST_CASE(
"CollisionDetection Complex Geometry - Multi-Patch Accuracy") {
5061 collision.disableMessages();
5062 collision.disableGPUAcceleration();
5065 std::vector<uint> uuids;
5068 for (
int i = 0; i < 5; i++) {
5069 for (
int j = 0; j < 5; j++) {
5073 uuids.push_back(uuid);
5078 int correct_predictions = 0;
5079 int total_predictions = 0;
5081 for (
int i = 0; i < 10; i++) {
5082 for (
int j = 0; j < 10; j++) {
5092 bool should_hit =
false;
5093 for (
int ti = 0; ti < 5; ti++) {
5094 for (
int tj = 0; tj < 5; tj++) {
5095 float px = ti * 0.8f;
5096 float py = tj * 0.8f;
5099 const float EPSILON = 1e-6f;
5100 if ((x >= px - 0.25f - EPSILON) && (x <= px + 0.25f + EPSILON) && (y >= py - 0.25f - EPSILON) && (y <= py + 0.25f + EPSILON)) {
5109 if (result.
hit == should_hit) {
5110 correct_predictions++;
5112 total_predictions++;
5117 float accuracy = (float) correct_predictions / (
float) total_predictions;
5118 DOCTEST_CHECK(accuracy == 1.0f);
5123DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Ray-Voxel Intersection") {
5126 collision.disableMessages();
5127 collision.disableGPUAcceleration();
5138 DOCTEST_CHECK(result.
hit ==
true);
5142 DOCTEST_CHECK(std::abs(result.
distance - 1.0f) < 1e-6f);
5150DOCTEST_TEST_CASE(
"CollisionDetection Mathematical Accuracy - Ray-Voxel Edge Cases") {
5153 collision.disableMessages();
5154 collision.disableGPUAcceleration();
5165 DOCTEST_CHECK(edge_result.
hit ==
true);
5167 DOCTEST_CHECK(std::abs(edge_result.
distance - 1.0f) < 1e-6f);
5178 DOCTEST_CHECK(corner_result.
hit ==
true);
5184DOCTEST_TEST_CASE(
"CollisionDetection Complex Geometry - Multi-Voxel Accuracy") {
5187 collision.disableMessages();
5188 collision.disableGPUAcceleration();
5191 std::vector<uint> uuids;
5194 for (
int i = 0; i < 3; i++) {
5195 for (
int j = 0; j < 3; j++) {
5196 for (
int k = 0; k < 3; k++) {
5201 uuids.push_back(uuid);
5207 int correct_predictions = 0;
5208 int total_predictions = 0;
5210 for (
int i = 0; i < 10; i++) {
5211 for (
int j = 0; j < 10; j++) {
5221 bool should_hit =
false;
5222 for (
int vi = 0; vi < 3; vi++) {
5223 for (
int vj = 0; vj < 3; vj++) {
5224 for (
int vk = 0; vk < 3; vk++) {
5225 float vx = vi * 1.5f;
5226 float vy = vj * 1.5f;
5227 float vz = vk * 1.5f;
5230 float voxel_x_min = vx - 0.5f;
5231 float voxel_x_max = vx + 0.5f;
5232 float voxel_y_min = vy - 0.5f;
5233 float voxel_y_max = vy + 0.5f;
5234 float voxel_z_min = vz - 0.5f;
5235 float voxel_z_max = vz + 0.5f;
5238 const float EPSILON = 1e-6f;
5239 if ((x >= voxel_x_min - EPSILON) && (x <= voxel_x_max + EPSILON) && (y >= voxel_y_min - EPSILON) && (y <= voxel_y_max + EPSILON)) {
5241 if (voxel_z_max >= -1.0f - EPSILON) {
5254 if (result.
hit == should_hit) {
5255 correct_predictions++;
5257 total_predictions++;
5262 float accuracy = (float) correct_predictions / (
float) total_predictions;
5263 DOCTEST_CHECK(accuracy == 1.0f);
5266DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - Basic getVoxelRayHitCounts Functionality") {
5269 collision.disableMessages();
5275 vec3 grid_center(0, 0, 0);
5276 vec3 grid_size(4, 4, 4);
5277 int3 grid_divisions(2, 2, 2);
5280 std::vector<vec3> ray_origins;
5281 std::vector<vec3> ray_directions;
5284 ray_origins.push_back(
make_vec3(0, 0, -3));
5285 ray_directions.push_back(
make_vec3(0, 0, 1));
5288 ray_origins.push_back(
make_vec3(-1.5, -1.5, -3));
5289 ray_directions.push_back(
make_vec3(0, 0, 1));
5292 ray_origins.push_back(
make_vec3(0, 0, -0.5));
5293 ray_directions.push_back(
make_vec3(0, 0, 1));
5296 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
5299 int hit_before, hit_after, hit_inside;
5300 collision.getVoxelRayHitCounts(
make_int3(1, 1, 0), hit_before, hit_after, hit_inside);
5303 DOCTEST_CHECK(hit_before >= 0);
5304 DOCTEST_CHECK(hit_after >= 0);
5305 DOCTEST_CHECK(hit_inside >= 0);
5308 collision.getVoxelRayHitCounts(
make_int3(0, 0, 0), hit_before, hit_after, hit_inside);
5311 DOCTEST_CHECK(hit_before >= 0);
5312 DOCTEST_CHECK(hit_after >= 0);
5313 DOCTEST_CHECK(hit_inside >= 0);
5316DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - getVoxelRayPathLengths Individual Lengths") {
5319 collision.disableMessages();
5322 vec3 grid_center(0, 0, 0);
5323 vec3 grid_size(2, 2, 2);
5324 int3 grid_divisions(1, 1, 1);
5327 std::vector<vec3> ray_origins;
5328 std::vector<vec3> ray_directions;
5331 ray_origins.push_back(
make_vec3(0, 0, -2));
5332 ray_directions.push_back(
make_vec3(0, 0, 1));
5335 ray_origins.push_back(
make_vec3(-2, -2, -2));
5336 ray_directions.push_back(normalize(
make_vec3(1, 1, 1)));
5339 ray_origins.push_back(
make_vec3(0, -2, -2));
5340 ray_directions.push_back(normalize(
make_vec3(0, 1, 1)));
5342 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
5345 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(
make_int3(0, 0, 0));
5348 DOCTEST_CHECK(path_lengths.size() >= 1);
5351 float max_diagonal = 2.0f * sqrt(3.0f);
5352 for (
float length: path_lengths) {
5353 DOCTEST_CHECK(length > 0.0f);
5354 DOCTEST_CHECK(length <= max_diagonal + 1e-6f);
5358 bool found_center_ray =
false;
5359 bool found_diagonal_ray =
false;
5361 for (
float length: path_lengths) {
5362 if (std::abs(length - 2.0f) < 0.1f) {
5363 found_center_ray =
true;
5365 if (std::abs(length - 2.0f * sqrt(3.0f)) < 0.1f) {
5366 found_diagonal_ray =
true;
5370 DOCTEST_CHECK(found_center_ray);
5373DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - Beer's Law Scenario with Geometry") {
5376 collision.disableMessages();
5382 vec3 grid_center(0, 0, 0);
5383 vec3 grid_size(6, 6, 6);
5384 int3 grid_divisions(3, 3, 3);
5387 std::vector<vec3> ray_origins;
5388 std::vector<vec3> ray_directions;
5390 int num_rays_per_axis = 10;
5391 for (
int i = 0; i < num_rays_per_axis; i++) {
5392 for (
int j = 0; j < num_rays_per_axis; j++) {
5393 float x = -2.5f + (5.0f * i) / (num_rays_per_axis - 1);
5394 float y = -2.5f + (5.0f * j) / (num_rays_per_axis - 1);
5396 ray_origins.push_back(
make_vec3(x, y, -4));
5397 ray_directions.push_back(
make_vec3(0, 0, 1));
5401 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
5404 int hit_before, hit_after, hit_inside;
5405 collision.getVoxelRayHitCounts(
make_int3(1, 1, 1), hit_before, hit_after, hit_inside);
5412 DOCTEST_CHECK(hit_before >= 0);
5413 DOCTEST_CHECK(hit_after >= 0);
5414 DOCTEST_CHECK(hit_inside >= 0);
5415 DOCTEST_CHECK((hit_before + hit_after + hit_inside) > 0);
5418 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(
make_int3(1, 1, 1));
5419 DOCTEST_CHECK(path_lengths.size() > 0);
5422 collision.getVoxelRayHitCounts(
make_int3(0, 0, 0), hit_before, hit_after, hit_inside);
5426 DOCTEST_CHECK(hit_before >= 0);
5427 DOCTEST_CHECK(hit_after >= 0);
5428 DOCTEST_CHECK(hit_inside >= 0);
5431DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - Edge Cases and Boundary Conditions") {
5434 collision.disableMessages();
5440 vec3 grid_center(0, 0, 0);
5441 vec3 grid_size(4, 4, 4);
5442 int3 grid_divisions(2, 2, 2);
5444 std::vector<vec3> ray_origins;
5445 std::vector<vec3> ray_directions;
5448 ray_origins.push_back(
make_vec3(0, 0, -0.5));
5449 ray_directions.push_back(
make_vec3(0, 0, 1));
5452 ray_origins.push_back(
make_vec3(-1.99, -1.99, -3));
5453 ray_directions.push_back(
make_vec3(0, 0, 1));
5456 ray_origins.push_back(
make_vec3(-2, 0, 0));
5457 ray_directions.push_back(
make_vec3(1, 0, 0));
5460 ray_origins.push_back(
make_vec3(-2, -2, -2));
5461 ray_directions.push_back(normalize(
make_vec3(1, 1, 1)));
5463 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
5466 for (
int i = 0; i < grid_divisions.
x; i++) {
5467 for (
int j = 0; j < grid_divisions.
y; j++) {
5468 for (
int k = 0; k < grid_divisions.
z; k++) {
5469 int hit_before, hit_after, hit_inside;
5473 collision.getVoxelRayHitCounts(voxel_idx, hit_before, hit_after, hit_inside);
5476 DOCTEST_CHECK(hit_before >= 0);
5477 DOCTEST_CHECK(hit_after >= 0);
5478 DOCTEST_CHECK(hit_inside >= 0);
5481 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(voxel_idx);
5482 for (
float length: path_lengths) {
5483 DOCTEST_CHECK(length > 0.0f);
5484 DOCTEST_CHECK(length < 100.0f);
5491DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - Error Handling and Invalid Inputs") {
5494 collision.disableMessages();
5497 bool caught_negative_exception =
false;
5498 bool caught_large_exception =
false;
5499 std::string negative_error_msg;
5500 std::string large_error_msg;
5507 int hit_before, hit_after, hit_inside;
5508 collision.getVoxelRayHitCounts(
make_int3(-1, 0, 0), hit_before, hit_after, hit_inside);
5509 }
catch (
const std::exception &e) {
5510 caught_negative_exception =
true;
5511 negative_error_msg = e.what();
5516 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(
make_int3(100, 100, 100));
5517 }
catch (
const std::exception &e) {
5518 caught_large_exception =
true;
5519 large_error_msg = e.what();
5524 DOCTEST_CHECK(caught_negative_exception);
5525 DOCTEST_CHECK(negative_error_msg.find(
"Invalid voxel indices") != std::string::npos);
5526 DOCTEST_CHECK(caught_large_exception);
5527 DOCTEST_CHECK(large_error_msg.find(
"Invalid voxel indices") != std::string::npos);
5530 int hit_before, hit_after, hit_inside;
5531 collision.getVoxelRayHitCounts(
make_int3(0, 0, 0), hit_before, hit_after, hit_inside);
5534 DOCTEST_CHECK(hit_before == 0);
5535 DOCTEST_CHECK(hit_after == 0);
5536 DOCTEST_CHECK(hit_inside == 0);
5538 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(
make_int3(0, 0, 0));
5539 DOCTEST_CHECK(path_lengths.empty());
5542DOCTEST_TEST_CASE(
"CollisionDetection Ray Classification - Beer's Law Integration Test") {
5545 collision.disableMessages();
5549 std::vector<uint> vegetation_uuids;
5552 for (
int i = 0; i < 3; i++) {
5553 for (
int j = 0; j < 3; j++) {
5554 if ((i + j) % 2 == 0) {
5555 float x = -2.0f + i * 2.0f;
5556 float y = -2.0f + j * 2.0f;
5557 float z = 1.0f + i * 0.5f;
5560 vegetation_uuids.push_back(patch_uuid);
5565 vec3 grid_center(0, 0, 0);
5566 vec3 grid_size(8, 8, 6);
5567 int3 grid_divisions(4, 4, 3);
5570 std::vector<vec3> ray_origins;
5571 std::vector<vec3> ray_directions;
5573 int rays_per_axis = 20;
5574 for (
int i = 0; i < rays_per_axis; i++) {
5575 for (
int j = 0; j < rays_per_axis; j++) {
5576 float x = -3.5f + (7.0f * i) / (rays_per_axis - 1);
5577 float y = -3.5f + (7.0f * j) / (rays_per_axis - 1);
5579 ray_origins.push_back(
make_vec3(x, y, -3));
5580 ray_directions.push_back(
make_vec3(0, 0, 1));
5584 collision.calculateVoxelRayPathLengths(grid_center, grid_size, grid_divisions, ray_origins, ray_directions);
5587 bool found_realistic_data =
false;
5589 for (
int i = 0; i < grid_divisions.
x; i++) {
5590 for (
int j = 0; j < grid_divisions.
y; j++) {
5591 for (
int k = 0; k < grid_divisions.
z; k++) {
5592 int hit_before, hit_after, hit_inside;
5593 collision.getVoxelRayHitCounts(
make_int3(i, j, k), hit_before, hit_after, hit_inside);
5595 std::vector<float> path_lengths = collision.getVoxelRayPathLengths(
make_int3(i, j, k));
5597 if (!path_lengths.empty() && (hit_before + hit_after + hit_inside) > 0) {
5598 found_realistic_data =
true;
5601 int P_denom = path_lengths.size();
5602 int P_trans = P_denom - hit_inside;
5604 DOCTEST_CHECK(P_trans >= 0);
5605 DOCTEST_CHECK(P_trans <= P_denom);
5608 float transmission_probability =
static_cast<float>(P_trans) /
static_cast<float>(P_denom);
5609 DOCTEST_CHECK(transmission_probability >= 0.0f);
5610 DOCTEST_CHECK(transmission_probability <= 1.0f);
5613 if (hit_inside > 0) {
5614 DOCTEST_CHECK(transmission_probability < 1.0f);
5619 float total_path_length = 0.0f;
5620 for (
float length: path_lengths) {
5621 total_path_length += length;
5623 float r_bar = total_path_length / P_denom;
5625 DOCTEST_CHECK(r_bar > 0.0f);
5626 DOCTEST_CHECK(r_bar < 10.0f);
5630 if (P_trans < P_denom && P_trans > 0) {
5631 float ln_arg =
static_cast<float>(P_trans) /
static_cast<float>(P_denom);
5632 DOCTEST_CHECK(ln_arg > 0.0f);
5633 DOCTEST_CHECK(ln_arg <= 1.0f);
5640 DOCTEST_CHECK(found_realistic_data);
5643DOCTEST_TEST_CASE(
"CollisionDetection calculateVoxelPathLengths Enhanced Method") {
5646 collision.disableMessages();
5653 std::vector<vec3> ray_directions;
5654 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.0f, 0.0f)));
5655 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.1f, 0.0f)));
5656 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.0f, 0.1f)));
5659 std::vector<vec3> voxel_centers;
5660 std::vector<vec3> voxel_sizes;
5662 voxel_centers.push_back(
make_vec3(2.0f, 0.0f, 0.0f));
5663 voxel_centers.push_back(
make_vec3(5.0f, 0.0f, 0.0f));
5664 voxel_centers.push_back(
make_vec3(2.0f, 3.0f, 0.0f));
5666 voxel_sizes.push_back(
make_vec3(1.0f, 1.0f, 1.0f));
5667 voxel_sizes.push_back(
make_vec3(1.0f, 1.0f, 1.0f));
5668 voxel_sizes.push_back(
make_vec3(1.0f, 1.0f, 1.0f));
5670 auto result = collision.calculateVoxelPathLengths(scan_origin, ray_directions, voxel_centers, voxel_sizes);
5673 DOCTEST_CHECK(result.size() == 3);
5676 DOCTEST_CHECK(result[0].size() > 0);
5679 DOCTEST_CHECK(result[1].size() > 0);
5683 DOCTEST_CHECK(result[2].size() >= 0);
5686 for (
size_t voxel_idx = 0; voxel_idx < 2; ++voxel_idx) {
5687 for (
const auto &hit: result[voxel_idx]) {
5688 DOCTEST_CHECK(hit.path_length > 0.0f);
5689 DOCTEST_CHECK(hit.path_length <= 2.0f);
5690 DOCTEST_CHECK(hit.hit ==
false);
5691 DOCTEST_CHECK(hit.distance == -1.0f);
5692 DOCTEST_CHECK(hit.primitive_UUID == 0);
5702 std::vector<vec3> ray_directions;
5703 for (
int i = 0; i < 1000; ++i) {
5704 float theta = i * 0.01f;
5705 ray_directions.push_back(normalize(
make_vec3(1.0f, sin(theta), cos(theta))));
5709 std::vector<vec3> voxel_centers;
5710 std::vector<vec3> voxel_sizes;
5711 for (
int x = 0; x < 10; ++x) {
5712 for (
int y = 0; y < 10; ++y) {
5713 voxel_centers.push_back(
make_vec3(x + 1.0f, y - 5.0f, 0.0f));
5714 voxel_sizes.push_back(
make_vec3(0.5f, 0.5f, 0.5f));
5719 auto start_time = std::chrono::high_resolution_clock::now();
5720 auto result = collision.calculateVoxelPathLengths(scan_origin, ray_directions, voxel_centers, voxel_sizes);
5721 auto end_time = std::chrono::high_resolution_clock::now();
5723 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
5726 DOCTEST_CHECK(duration.count() < 500);
5729 DOCTEST_CHECK(result.size() == 100);
5732 size_t total_intersections = 0;
5733 for (
size_t i = 0; i < 100; ++i) {
5734 total_intersections += result[i].size();
5735 for (
const auto &hit: result[i]) {
5736 DOCTEST_CHECK(hit.path_length > 0.0f);
5737 DOCTEST_CHECK(hit.path_length <= 1.0f);
5742 DOCTEST_CHECK(total_intersections > 0);
5750 std::vector<vec3> empty_rays;
5751 std::vector<vec3> voxel_centers = {
make_vec3(1.0f, 0.0f, 0.0f)};
5752 std::vector<vec3> voxel_sizes = {
make_vec3(1.0f, 1.0f, 1.0f)};
5754 auto result = collision.calculateVoxelPathLengths(scan_origin, empty_rays, voxel_centers, voxel_sizes);
5755 DOCTEST_CHECK(result.empty());
5758 std::vector<vec3> ray_directions = {normalize(
make_vec3(1.0f, 0.0f, 0.0f))};
5759 std::vector<vec3> empty_voxels;
5760 std::vector<vec3> empty_sizes;
5762 result = collision.calculateVoxelPathLengths(scan_origin, ray_directions, empty_voxels, empty_sizes);
5763 DOCTEST_CHECK(result.empty());
5766 std::vector<vec3> mismatched_sizes = {
make_vec3(1.0f, 1.0f, 1.0f),
make_vec3(2.0f, 2.0f, 2.0f)};
5768 bool threw_exception =
false;
5772 collision.calculateVoxelPathLengths(scan_origin, ray_directions, voxel_centers, mismatched_sizes);
5773 }
catch (
const std::exception &) {
5774 threw_exception =
true;
5778 DOCTEST_CHECK(threw_exception);
5784 vec3 ray_direction = normalize(
make_vec3(1.0f, 0.0f, 0.0f));
5788 auto result = collision.calculateVoxelPathLengths(scan_origin, {ray_direction}, {voxel_center}, {voxel_size});
5790 DOCTEST_CHECK(result.size() == 1);
5791 DOCTEST_CHECK(result[0].size() == 1);
5795 DOCTEST_CHECK(std::abs(path_length - 2.0f) < 1e-4f);
5801 std::vector<vec3> ray_directions;
5804 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.0f, 0.0f)));
5805 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.2f, 0.0f)));
5806 ray_directions.push_back(normalize(
make_vec3(1.0f, 0.0f, 0.2f)));
5811 auto result = collision.calculateVoxelPathLengths(scan_origin, ray_directions, {voxel_center}, {voxel_size});
5814 DOCTEST_CHECK(result.size() == 1);
5815 DOCTEST_CHECK(result[0].size() == 3);
5818 std::vector<float> path_lengths;
5819 for (
const auto &hit: result[0]) {
5820 path_lengths.push_back(hit.path_length);
5824 for (
float path: path_lengths) {
5825 DOCTEST_CHECK(path > 0.5f);
5826 DOCTEST_CHECK(path < 2.0f);
5831 DOCTEST_CHECK(!(path_lengths[0] == path_lengths[1] && path_lengths[1] == path_lengths[2]));
5837 std::vector<vec3> ray_directions = {normalize(
make_vec3(1.0f, 0.0f, 0.0f)), normalize(
make_vec3(1.0f, 0.1f, 0.0f)), normalize(
make_vec3(1.0f, 0.0f, 0.1f))};
5839 std::vector<vec3> voxel_centers = {
make_vec3(2.0f, 0.0f, 0.0f),
make_vec3(5.0f, 0.0f, 0.0f)};
5841 std::vector<vec3> voxel_sizes = {
make_vec3(1.0f, 1.0f, 1.0f),
make_vec3(1.0f, 1.0f, 1.0f)};
5844 auto result = collision.calculateVoxelPathLengths(scan_origin, ray_directions, voxel_centers, voxel_sizes);
5847 for (
size_t c = 0; c < voxel_centers.size(); ++c) {
5848 std::vector<float> dr_agg;
5849 uint hit_after_agg = 0;
5852 for (
const auto &hit: result[c]) {
5853 dr_agg.push_back(hit.path_length);
5858 DOCTEST_CHECK(hit_after_agg == result[c].size());
5859 for (
float path_length: dr_agg) {
5860 DOCTEST_CHECK(path_length > 0.0f);
5861 DOCTEST_CHECK(path_length <= 2.0f);
5867DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Patch with no slices") {
5874 uint prim_UUID =
context.addPatch(patch_center, patch_size);
5880 collisiondetection.disableMessages();
5882 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(2, 2, 2));
5884 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
5887DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Patch with one slice") {
5894 uint prim_UUID =
context.addPatch(patch_center, patch_size);
5900 collisiondetection.disableMessages();
5902 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(2, 1, 1));
5904 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
5907DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Patch with 2 slices") {
5914 uint prim_UUID =
context.addPatch(patch_center, patch_size);
5920 collisiondetection.disableMessages();
5922 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(3, 1, 1));
5924 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
5927DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Patch with 3 slices and no vertices inside voxel") {
5934 uint prim_UUID =
context.addPatch(patch_center, patch_size);
5940 collisiondetection.disableMessages();
5942 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(4, 1, 1));
5944 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
5947DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Areas after slicing (non-textured)") {
5954 uint prim_UUID =
context.addPatch(patch_center, patch_size);
5956 float area_patch =
context.getPrimitiveArea(prim_UUID);
5962 collisiondetection.disableMessages();
5964 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(2, 1, 2));
5967 for (
uint UUID: voxel_UUIDs) {
5968 area_tot +=
context.getPrimitiveArea(UUID);
5971 DOCTEST_CHECK(fabs(area_tot - area_patch) / area_patch < 0.05f);
5974DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Textured areas after slicing") {
5983 float area_patch =
context.getPrimitiveArea(prim_UUID);
5989 collisiondetection.disableMessages();
5991 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(2, 1, 2));
5994 for (
uint UUID: voxel_UUIDs) {
5995 area_tot +=
context.getPrimitiveArea(UUID);
5998 DOCTEST_CHECK(fabs(area_tot - area_patch) / area_patch < 0.05f);
6001DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Cropping non-textured primitives") {
6007 uint prim_UUID = tri1_UUID;
6013 collisiondetection.disableMessages();
6016 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(1, 1, 1));
6018 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
6021DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Cropping textured primitives") {
6034 collisiondetection.disableMessages();
6037 std::vector<uint> voxel_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(1, 1, 1));
6039 DOCTEST_CHECK(voxel_UUIDs.size() >= 1);
6042DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - Basic functionality test") {
6049 uint prim_UUID =
context.addPatch(patch_center, patch_size);
6052 collisiondetection.disableMessages();
6057 std::vector<uint> sliced_UUIDs = collisiondetection.slicePrimitivesUsingGrid(std::vector<uint>{prim_UUID}, grid_center, grid_size,
make_int3(2, 1, 2));
6060 DOCTEST_CHECK(sliced_UUIDs.size() >= 1);
6067DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - calculatePrimitiveVoxelIntersection Basic") {
6070 collisiondetection.disableMessages();
6075 uint voxel_uuid =
context.addVoxel(voxel_center, voxel_size);
6080 uint patch_uuid =
context.addPatch(patch_center, patch_size);
6084 uint outside_uuid =
context.addPatch(outside_center, patch_size);
6087 collisiondetection.calculatePrimitiveVoxelIntersection();
6090 DOCTEST_CHECK(
context.doesPrimitiveDataExist(voxel_uuid,
"inside_UUIDs"));
6092 std::vector<uint> inside_prims;
6093 context.getPrimitiveData(voxel_uuid,
"inside_UUIDs", inside_prims);
6095 DOCTEST_CHECK(inside_prims.size() == 1);
6096 DOCTEST_CHECK(inside_prims[0] == patch_uuid);
6099DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - calculatePrimitiveVoxelIntersection Axis-Aligned") {
6102 collisiondetection.disableMessages();
6117 collisiondetection.calculatePrimitiveVoxelIntersection();
6120 DOCTEST_CHECK(
context.doesPrimitiveDataExist(voxel_uuid,
"inside_UUIDs"));
6122 std::vector<uint> inside_prims;
6123 context.getPrimitiveData(voxel_uuid,
"inside_UUIDs", inside_prims);
6125 DOCTEST_CHECK(inside_prims.size() == 3);
6128DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - calculatePrimitiveVoxelIntersection Multiple Voxels") {
6131 collisiondetection.disableMessages();
6144 collisiondetection.calculatePrimitiveVoxelIntersection();
6147 std::vector<uint> inside1;
6148 context.getPrimitiveData(voxel1,
"inside_UUIDs", inside1);
6149 DOCTEST_CHECK(inside1.size() == 1);
6150 DOCTEST_CHECK(inside1[0] == patch1);
6153 std::vector<uint> inside2;
6154 context.getPrimitiveData(voxel2,
"inside_UUIDs", inside2);
6155 DOCTEST_CHECK(inside2.size() == 1);
6156 DOCTEST_CHECK(inside2[0] == patch2);
6159 std::vector<uint> inside3;
6160 context.getPrimitiveData(voxel3,
"inside_UUIDs", inside3);
6161 DOCTEST_CHECK(inside3.size() == 2);
6164DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - calculatePrimitiveVoxelIntersection Empty Inputs") {
6167 collisiondetection.disableMessages();
6171 collisiondetection.calculatePrimitiveVoxelIntersection();
6177 collisiondetection2.disableMessages();
6179 collisiondetection2.calculatePrimitiveVoxelIntersection();
6184DOCTEST_TEST_CASE(
"CollisionDetection VoxelIntersection Integration - calculatePrimitiveVoxelIntersection Specific UUIDs") {
6187 collisiondetection.disableMessages();
6196 std::vector<uint> test_uuids = {voxel_uuid, patch1, patch3};
6197 collisiondetection.calculatePrimitiveVoxelIntersection(test_uuids);
6200 std::vector<uint> inside_prims;
6201 context.getPrimitiveData(voxel_uuid,
"inside_UUIDs", inside_prims);
6203 DOCTEST_CHECK(inside_prims.size() == 1);
6204 DOCTEST_CHECK(inside_prims[0] == patch1);
6207DOCTEST_TEST_CASE(
"CollisionDetection GPU/CPU Ray Casting Parity") {
6214 cd.disableMessages();
6219 float wall_size = 2.0f;
6220 float spacing = wall_size / grid_size;
6222 for (
int iy = 0; iy < grid_size; iy++) {
6223 for (
int iz = 0; iz < grid_size; iz++) {
6224 float y = -wall_size/2.0f + iy * spacing;
6225 float z = -wall_size/2.0f + iz * spacing;
6229 vec3 v1(0, y + spacing, z);
6230 vec3 v2(0, y + spacing, z + spacing);
6231 vec3 v3(0, y, z + spacing);
6233 context.addTriangle(v0, v1, v2);
6234 context.addTriangle(v0, v2, v3);
6238 uint triangle_count =
context.getPrimitiveCount();
6239 std::cout <<
"Test geometry: " << triangle_count <<
" triangles" << std::endl;
6245 std::vector<CollisionDetection::RayQuery> ray_queries;
6246 int rays_per_dim = 1050;
6248 for (
int iy = 0; iy < rays_per_dim; iy++) {
6249 for (
int iz = 0; iz < rays_per_dim; iz++) {
6250 float y = -wall_size/2.0f + (iy + 0.5f) * wall_size / rays_per_dim;
6251 float z = -wall_size/2.0f + (iz + 0.5f) * wall_size / rays_per_dim;
6252 vec3 origin(-2.0f, y, z);
6253 vec3 direction(1.0f, 0.0f, 0.0f);
6254 ray_queries.emplace_back(origin, direction, 10.0f);
6258 std::cout <<
"Casting " << ray_queries.size() <<
" rays through wall (testing GPU path)..." << std::endl;
6261 cd.disableGPUAcceleration();
6262 std::vector<CollisionDetection::HitResult> cpu_results = cd.castRays(ray_queries);
6264 size_t cpu_hits = 0;
6265 for (
const auto& result : cpu_results) {
6266 if (result.
hit) cpu_hits++;
6270 cd.enableGPUAcceleration();
6271 std::vector<CollisionDetection::HitResult> gpu_results = cd.castRays(ray_queries);
6273 size_t gpu_hits = 0;
6274 for (
const auto& result : gpu_results) {
6275 if (result.
hit) gpu_hits++;
6278 std::cout <<
"CPU hits: " << cpu_hits <<
" (" << (100.0*cpu_hits/ray_queries.size()) <<
"%)" << std::endl;
6279 std::cout <<
"GPU hits: " << gpu_hits <<
" (" << (100.0*gpu_hits/ray_queries.size()) <<
"%)" << std::endl;
6282 float cpu_hit_rate =
static_cast<float>(cpu_hits) / ray_queries.size();
6283 DOCTEST_CHECK(cpu_hit_rate > 0.90f);
6288 float hit_ratio =
static_cast<float>(gpu_hits) /
static_cast<float>(cpu_hits);
6289 DOCTEST_CHECK(hit_ratio > 0.95f);
6290 DOCTEST_CHECK(hit_ratio < 1.05f);
6294DOCTEST_TEST_CASE(
"CollisionDetection Flat Single-Plane Mesh Ray Casting") {
6300 collision.disableMessages();
6301 collision.disableGPUAcceleration();
6306 std::vector<uint> sheet_UUIDs;
6308 for (
int i = 0; i < N; i++) {
6309 for (
int j = 0; j < N; j++) {
6310 float x =
static_cast<float>(i);
6311 float y =
static_cast<float>(j);
6317 collision.buildBVH();
6324 for (
int i = 0; i < N; i++) {
6325 for (
int j = 0; j < N; j++) {
6326 vec3 origin =
make_vec3(
static_cast<float>(i) + 0.5f,
static_cast<float>(j) + 0.5f, 5.0f);
6335 DOCTEST_CHECK(hit_count == total);
6340 vec3 oblique_dir = normalize(
make_vec3(0.0f, 1.0f, -0.05f));
6342 DOCTEST_CHECK(oblique.
hit ==
true);