27#elif defined(__SSE4_1__)
29#elif defined(__SSE2__)
39#ifdef HELIOS_CUDA_AVAILABLE
40#include <cuda_runtime.h>
43using namespace helios;
45#ifdef HELIOS_CUDA_AVAILABLE
56void launchBVHTraversal(
void *h_nodes,
int node_count,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_query_aabb_min,
float *h_query_aabb_max,
int num_queries,
57 unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_query);
58bool launchVoxelRayPathLengths(
int num_rays,
float *h_ray_origins,
float *h_ray_directions,
float grid_center_x,
float grid_center_y,
float grid_center_z,
float grid_size_x,
float grid_size_y,
float grid_size_z,
int grid_divisions_x,
59 int grid_divisions_y,
int grid_divisions_z,
int primitive_count,
int *h_voxel_ray_counts,
float *h_voxel_path_lengths,
int *h_voxel_transmitted,
int *h_voxel_hit_before,
int *h_voxel_hit_after,
int *h_voxel_hit_inside);
61void launchWarpEfficientBVH(
void *h_bvh_soa_gpu,
unsigned int *h_primitive_indices,
int primitive_count,
float *h_primitive_aabb_min,
float *h_primitive_aabb_max,
float *h_ray_origins,
float *h_ray_directions,
float *h_ray_max_distances,
62 int num_rays,
unsigned int *h_results,
unsigned int *h_result_counts,
int max_results_per_ray);
65void launchRaysOnResidentScene(
void *d_bvh_nodes,
int node_count,
unsigned int *d_primitive_indices,
int primitive_count,
int *d_primitive_types, float3 *d_primitive_vertices,
unsigned int *d_vertex_offsets,
const unsigned char *d_mask_data,
66 const unsigned int *d_mask_offsets,
const int *d_mask_sizes,
const int *d_mask_IDs,
const float *d_uv_data,
const int *d_uv_IDs,
int total_vertex_count,
const float *h_ray_origins,
const float *h_ray_directions,
67 const float *h_ray_max_distances,
float uniform_max_distance,
int num_rays,
float *h_hit_distances,
unsigned int *h_hit_primitive_ids,
unsigned int *h_hit_counts,
float *h_hit_normals,
bool find_closest_hit);
72 return make_float3(v.
x, v.
y, v.
z);
86 vec3 ray_direction = direction;
90 ray_direction = ray_direction / ray_direction.
magnitude();
97 if (!bvh_nodes.empty()) {
99 ensurePrimitiveCacheCurrent();
102 RayQuery query(origin, ray_direction, max_distance, target_UUIDs);
103 return castRayBVHTraversal(query);
108 std::vector<uint> search_primitives;
109 if (target_UUIDs.empty()) {
111 if (!primitive_cache.empty()) {
113 search_primitives.reserve(primitive_cache.size());
114 for (
const auto &cached_pair: primitive_cache) {
115 search_primitives.push_back(cached_pair.first);
119 search_primitives =
context->getAllUUIDs();
122 search_primitives = target_UUIDs;
126 float nearest_distance = std::numeric_limits<float>::max();
127 if (max_distance > 0) {
128 nearest_distance = max_distance;
131 uint hit_primitive = 0;
132 bool found_intersection =
false;
135 for (
uint candidate_uuid: search_primitives) {
136 float intersection_distance;
140 if (!primitive_cache.empty()) {
141 HitResult primitive_result = intersectPrimitiveThreadSafe(origin, ray_direction, candidate_uuid, max_distance);
142 if (primitive_result.
hit) {
144 intersection_distance = primitive_result.
distance;
148 if (!
context->doesPrimitiveExist(candidate_uuid)) {
151 hit = rayPrimitiveIntersection(origin, ray_direction, candidate_uuid, intersection_distance);
154 if (hit &&
context->primitiveTextureHasTransparencyChannel(candidate_uuid)) {
155 CachedPrimitive temp_cached(
context->getPrimitiveType(candidate_uuid),
context->getPrimitiveVertices(candidate_uuid));
156 temp_cached.transparency_mask =
context->getPrimitiveTextureTransparencyData(candidate_uuid);
157 temp_cached.texture_size =
context->getPrimitiveTextureSize(candidate_uuid);
158 temp_cached.uv =
context->getPrimitiveTextureUV(candidate_uuid);
159 if (!isHitTexelOpaque(temp_cached, origin + ray_direction * intersection_distance)) {
167 if (intersection_distance > 1e-6f &&
168 intersection_distance < nearest_distance) {
170 nearest_distance = intersection_distance;
171 hit_primitive = candidate_uuid;
172 found_intersection =
true;
178 if (found_intersection) {
187 std::vector<vec3> vertices =
context->getPrimitiveVertices(hit_primitive);
191 vec3 v0 = vertices[0];
192 vec3 v1 = vertices[1];
193 vec3 v2 = vertices[2];
194 vec3 edge1 = v1 - v0;
195 vec3 edge2 = v2 - v0;
200 vec3 v0 = vertices[0];
201 vec3 v1 = vertices[1];
202 vec3 v2 = vertices[2];
203 vec3 edge1 = v1 - v0;
204 vec3 edge2 = v2 - v0;
211 }
catch (
const std::exception &e) {
221bool CollisionDetection::shouldUseGPU(
size_t ray_count)
const {
222#ifdef HELIOS_CUDA_AVAILABLE
225 constexpr size_t GPU_BATCH_THRESHOLD = 1000000;
226 constexpr size_t MIN_PRIMITIVES_FOR_GPU = 500;
227 return gpu_acceleration_enabled && ray_count >= GPU_BATCH_THRESHOLD && d_bvh_nodes !=
nullptr && d_primitive_vertices !=
nullptr && !primitive_indices.empty() && primitive_indices.size() >= MIN_PRIMITIVES_FOR_GPU;
235 std::vector<HitResult> results;
236 results.reserve(ray_queries.size());
245#ifdef HELIOS_CUDA_AVAILABLE
246 if (shouldUseGPU(ray_queries.size())) {
247 castRaysGPU(ray_queries, results, local_stats);
249 castRaysCPU(ray_queries, results, local_stats);
253 castRaysCPU(ray_queries, results, local_stats);
257 if (stats !=
nullptr) {
258 *stats = local_stats;
264void CollisionDetection::castRaysCPU(
const std::vector<RayQuery> &ray_queries, std::vector<HitResult> &results, RayTracingStats &stats) {
269#ifdef HELIOS_CUDA_AVAILABLE
270void CollisionDetection::castRaysGPU(
const std::vector<RayQuery> &ray_queries, std::vector<HitResult> &results, RayTracingStats &stats) {
272 results = castRaysGPU(ray_queries, stats);
277 const std::vector<RayQuery> &ray_queries) {
280 std::vector<std::vector<std::vector<std::vector<HitResult>>>> grid_results;
281 grid_results.resize(grid_divisions.
x);
282 for (
int i = 0; i < grid_divisions.
x; i++) {
283 grid_results[i].resize(grid_divisions.
y);
284 for (
int j = 0; j < grid_divisions.
y; j++) {
285 grid_results[i][j].resize(grid_divisions.
z);
290 vec3 voxel_size =
make_vec3(grid_size.
x /
float(grid_divisions.
x), grid_size.
y /
float(grid_divisions.
y), grid_size.
z /
float(grid_divisions.
z));
293 for (
const auto &query: ray_queries) {
296 if (hit_result.
hit) {
300 int voxel_i = int(relative_pos.
x / voxel_size.
x);
301 int voxel_j = int(relative_pos.
y / voxel_size.
y);
302 int voxel_k = int(relative_pos.
z / voxel_size.
z);
305 if (voxel_i >= 0 && voxel_i < grid_divisions.x && voxel_j >= 0 && voxel_j < grid_divisions.y && voxel_k >= 0 && voxel_k < grid_divisions.
z) {
307 grid_results[voxel_i][voxel_j][voxel_k].push_back(hit_result);
317 if (ray_directions.empty()) {
319 std::cout <<
"WARNING (CollisionDetection::calculateVoxelPathLengths): No rays provided" << std::endl;
321 return std::vector<std::vector<HitResult>>();
324 if (voxel_centers.size() != voxel_sizes.size()) {
325 helios_runtime_error(
"ERROR (CollisionDetection::calculateVoxelPathLengths): voxel_centers and voxel_sizes vectors must have same size");
328 if (voxel_centers.empty()) {
330 std::cout <<
"WARNING (CollisionDetection::calculateVoxelPathLengths): No voxels provided" << std::endl;
332 return std::vector<std::vector<HitResult>>();
335 const size_t num_rays = ray_directions.size();
336 const size_t num_voxels = voxel_centers.size();
339 std::cout <<
"Calculating voxel path lengths for " << num_rays <<
" rays through " << num_voxels <<
" voxels..." << std::endl;
343 std::vector<std::vector<HitResult>> result(num_voxels);
346#pragma omp parallel for schedule(dynamic, 1000)
347 for (
int ray_idx = 0; ray_idx < static_cast<int>(num_rays); ++ray_idx) {
348 const vec3 &ray_direction = ray_directions[ray_idx];
351 for (
size_t voxel_idx = 0; voxel_idx < num_voxels; ++voxel_idx) {
352 const vec3 &voxel_center = voxel_centers[voxel_idx];
353 const vec3 &voxel_size = voxel_sizes[voxel_idx];
356 const vec3 half_size = voxel_size * 0.5f;
357 const vec3 voxel_min = voxel_center - half_size;
358 const vec3 voxel_max = voxel_center + half_size;
362 if (rayAABBIntersect(scan_origin, ray_direction, voxel_min, voxel_max, t_min, t_max)) {
364 const float path_length = t_max - std::max(0.0f, t_min);
366 if (path_length > 1e-6f) {
369 hit_result.
hit =
false;
379 result[voxel_idx].push_back(hit_result);
387 size_t total_intersections = 0;
388 for (
size_t i = 0; i < num_voxels; ++i) {
389 total_intersections += result[i].size();
391 std::cout <<
"Completed voxel path length calculations. Total ray-voxel intersections: " << total_intersections << std::endl;
398 std::vector<HitResult> &hit_results) {
401 hit_results.reserve(ray_origins.size());
403 if (ray_origins.size() != ray_directions.size()) {
404 helios_runtime_error(
"ERROR (CollisionDetection::calculateRayPathLengthsDetailed): ray_origins and ray_directions must have the same size");
412 for (
size_t i = 0; i < ray_origins.size(); i++) {
413 RayQuery query(ray_origins[i], ray_directions[i]);
415 hit_results.push_back(result);
424 if (mode == bvh_optimization_mode) {
429 bvh_optimization_mode = mode;
432 if (old_mode != mode && !bvh_nodes.empty()) {
434 std::cout <<
"CollisionDetection: Converting BVH from mode " <<
static_cast<int>(old_mode) <<
" to mode " <<
static_cast<int>(mode) << std::endl;
438 ensureOptimizedBVH();
441 auto memory_stats = getBVHMemoryUsage();
442 std::cout <<
"CollisionDetection: Memory usage - SoA: " << memory_stats.soa_memory_bytes <<
" bytes, Quantized: " << memory_stats.quantized_memory_bytes <<
" bytes (" << memory_stats.quantized_reduction_percent <<
"% reduction)"
449 return bvh_optimization_mode;
452void CollisionDetection::convertBVHLayout(BVHOptimizationMode from_mode, BVHOptimizationMode to_mode) {
459 if (ray_queries.empty()) {
465 ensureOptimizedBVH();
468 ensurePrimitiveCacheCurrent();
471 std::vector<HitResult> results;
472 results.reserve(ray_queries.size());
474 auto start_time = std::chrono::high_resolution_clock::now();
477 switch (bvh_optimization_mode) {
483 auto end_time = std::chrono::high_resolution_clock::now();
484 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
487 *stats = local_stats;
494 if (ray_stream.
packets.empty()) {
501 for (
auto &packet: ray_stream.
packets) {
503 auto queries = packet.toRayQueries();
509 if (results.size() != queries.size()) {
515 packet.results = std::move(results);
525 *stats = combined_stats;
528 if (printmessages && success) {
529 std::cout <<
"CollisionDetection: Processed " << ray_stream.
packets.size() <<
" ray packets (" << ray_stream.
total_rays <<
" total rays)" << std::endl;
539 MemoryUsageStats stats;
542 stats.soa_memory_bytes = bvh_nodes_soa.getMemoryUsage();
545 stats.quantized_memory_bytes = 0;
546 stats.quantized_reduction_percent = 0.0f;
557 constexpr uint MISS_UUID = 0xFFFFFFFFu;
563 if (stats !=
nullptr) {
564 *stats = local_stats;
572 volatile int *
const cancel = cancel_flag;
573 if (cancel !=
nullptr && *cancel != 0) {
574 for (
size_t i = 0; i < count; i++) {
575 out_primitive_UUID[i] = MISS_UUID;
577 if (stats !=
nullptr) {
578 *stats = local_stats;
588#ifdef HELIOS_CUDA_AVAILABLE
594 if (shouldUseGPU(count)) {
597 const float kernel_max_distance = (max_distance > 0) ? max_distance : std::numeric_limits<float>::max();
604 launchRaysOnResidentScene(d_bvh_nodes, d_gpu_node_count, d_primitive_indices, d_gpu_primitive_count, d_primitive_types, (float3 *) d_primitive_vertices, d_vertex_offsets, (
const unsigned char *) d_mask_data, d_mask_offsets, d_mask_sizes,
605 d_mask_IDs, (
const float *) d_uv_data, d_uv_IDs, d_gpu_total_vertex_count,
reinterpret_cast<const float *
>(origins),
reinterpret_cast<const float *
>(directions),
nullptr, kernel_max_distance,
606 static_cast<int>(count), out_distance, out_primitive_UUID,
nullptr,
reinterpret_cast<float *
>(out_normal),
true);
610 size_t total_hits = 0;
611 double dist_sum = 0.0;
612 for (
size_t i = 0; i < count; i++) {
613 if (out_primitive_UUID[i] != MISS_UUID) {
615 dist_sum += out_distance[i];
619 local_stats.
average_ray_distance = (total_hits > 0) ? (dist_sum /
static_cast<double>(total_hits)) : 0.0;
620 if (stats !=
nullptr) {
621 *stats = local_stats;
627 ensureOptimizedBVH();
628 ensurePrimitiveCacheCurrent();
630 if (bvh_nodes_soa.node_count == 0) {
632 for (
size_t i = 0; i < count; i++) {
633 out_primitive_UUID[i] = MISS_UUID;
635 if (stats !=
nullptr) {
636 *stats = local_stats;
645#pragma omp for schedule(guided, 32)
646 for (
long long i = 0; i < static_cast<long long>(count); ++i) {
647 if (cancel !=
nullptr && *cancel != 0) {
648 out_primitive_UUID[i] = MISS_UUID;
651 RayQuery query(origins[i], directions[i], max_distance);
652 HitResult result = castRaySoATraversal(query, thread_stats);
657 out_normal[i] = result.
normal;
661 out_primitive_UUID[i] = MISS_UUID;
677 if (stats !=
nullptr) {
678 *stats = local_stats;
684 constexpr uint MISS_UUID = 0xFFFFFFFFu;
690 if (stats !=
nullptr) {
691 *stats = local_stats;
697 if (packet_size <= 1) {
698 castRaysSoA(origins, directions, count, max_distance, out_distance, out_normal, out_primitive_UUID, stats);
702 volatile int *
const cancel = cancel_flag;
703 if (cancel !=
nullptr && *cancel != 0) {
704 for (
size_t i = 0; i < count; i++) {
705 out_primitive_UUID[i] = MISS_UUID;
707 if (stats !=
nullptr) {
708 *stats = local_stats;
718 if (shouldUseGPU(count)) {
719 castRaysSoA(origins, directions, count, max_distance, out_distance, out_normal, out_primitive_UUID, stats);
723 ensureOptimizedBVH();
724 ensurePrimitiveCacheCurrent();
726 if (bvh_nodes_soa.node_count == 0) {
727 for (
size_t i = 0; i < count; i++) {
728 out_primitive_UUID[i] = MISS_UUID;
730 if (stats !=
nullptr) {
731 *stats = local_stats;
736 const size_t num_packets = (count + packet_size - 1) / packet_size;
742#pragma omp for schedule(guided, 8)
743 for (
long long pkt = 0; pkt < static_cast<long long>(num_packets); ++pkt) {
744 const size_t begin = size_t(pkt) * packet_size;
745 const size_t end = std::min(begin + packet_size, count);
747 if (cancel !=
nullptr && *cancel != 0) {
748 for (
size_t i = begin; i < end; i++) {
749 out_primitive_UUID[i] = MISS_UUID;
754 castPacketSoATraversal(origins, directions, begin, end, max_distance, out_distance, out_normal, out_primitive_UUID, thread_stats);
768 if (stats !=
nullptr) {
769 *stats = local_stats;
773void CollisionDetection::castPacketSoATraversal(
const helios::vec3 *origins,
const helios::vec3 *directions,
size_t begin,
size_t end,
float max_distance,
float *out_distance,
helios::vec3 *out_normal,
uint *out_primitive_UUID,
774 RayTracingStats &stats) {
775 constexpr uint MISS_UUID = 0xFFFFFFFFu;
776 const size_t n = end - begin;
780 constexpr size_t MAX_PACKET_RAYS = 256;
781 if (n > MAX_PACKET_RAYS) {
782 for (
size_t sub = begin; sub < end; sub += MAX_PACKET_RAYS) {
783 castPacketSoATraversal(origins, directions, sub, std::min(sub + MAX_PACKET_RAYS, end), max_distance, out_distance, out_normal, out_primitive_UUID, stats);
788 const bool use_dense_cache = (primitive_cache_dense.size() == primitive_indices.size());
791 float closest[MAX_PACKET_RAYS];
792 HitResult best[MAX_PACKET_RAYS];
793 vec3 ray_origin[MAX_PACKET_RAYS];
794 vec3 ray_dir[MAX_PACKET_RAYS];
795 const float init_far = (max_distance > 0) ? max_distance : std::numeric_limits<float>::
max();
796 for (
size_t r = 0; r < n; r++) {
797 ray_origin[r] = origins[begin + r];
798 ray_dir[r] = directions[begin + r];
799 closest[r] = init_far;
804 constexpr int STACK_CAPACITY = 128;
805 uint32_t node_stack[STACK_CAPACITY];
807 node_stack[stack_size++] = 0;
809 while (stack_size > 0) {
810 uint32_t node_idx = node_stack[--stack_size];
811 if (node_idx >= bvh_nodes_soa.node_count) {
815 if (bvh_nodes_soa.is_leaf_flags[node_idx]) {
820 int active_idx[MAX_PACKET_RAYS];
821 int active_count = 0;
822 for (
size_t r = 0; r < n; r++) {
823 if (aabbIntersectSoA(ray_origin[r], ray_dir[r], closest[r], node_idx)) {
824 active_idx[active_count++] = int(r);
827 if (active_count == 0) {
830 stats.bvh_nodes_visited++;
832 const uint32_t primitive_start = bvh_nodes_soa.primitive_starts[node_idx];
833 const uint32_t primitive_count = bvh_nodes_soa.primitive_counts[node_idx];
835 for (uint32_t i = 0; i < primitive_count; ++i) {
836 const uint32_t slot = primitive_start + i;
837 const uint primitive_id = primitive_indices[slot];
840 for (
int a = 0; a < active_count; ++a) {
841 const int r = active_idx[a];
842 HitResult pr = use_dense_cache ? intersectCachedPrimitive(ray_origin[r], ray_dir[r], primitive_cache_dense[slot], closest[r]) : intersectPrimitiveThreadSafe(ray_origin[r], ray_dir[r], primitive_id, closest[r]);
843 if (pr.hit && pr.distance < closest[r]) {
852 bool any_hit_box =
false;
853 for (
size_t r = 0; r < n; r++) {
854 if (aabbIntersectSoA(ray_origin[r], ray_dir[r], closest[r], node_idx)) {
862 stats.bvh_nodes_visited++;
864 const uint32_t left_child = bvh_nodes_soa.left_children[node_idx];
865 const uint32_t right_child = bvh_nodes_soa.right_children[node_idx];
866 const bool left_valid = (left_child != 0xFFFFFFFF && left_child < bvh_nodes_soa.node_count);
867 const bool right_valid = (right_child != 0xFFFFFFFF && right_child < bvh_nodes_soa.node_count);
869 if (left_valid && right_valid) {
873 const float t_left = aabbEntryDistanceSoA(ray_origin[0], ray_dir[0], left_child);
874 const float t_right = aabbEntryDistanceSoA(ray_origin[0], ray_dir[0], right_child);
875 uint32_t first = left_child, second = right_child;
876 if (t_right < t_left) {
880 if (stack_size < STACK_CAPACITY)
881 node_stack[stack_size++] = second;
882 if (stack_size < STACK_CAPACITY)
883 node_stack[stack_size++] = first;
884 }
else if (left_valid) {
885 if (stack_size < STACK_CAPACITY)
886 node_stack[stack_size++] = left_child;
887 }
else if (right_valid) {
888 if (stack_size < STACK_CAPACITY)
889 node_stack[stack_size++] = right_child;
895 for (
size_t r = 0; r < n; r++) {
897 out_primitive_UUID[begin + r] = best[r].primitive_UUID;
898 out_distance[begin + r] = best[r].distance;
899 out_normal[begin + r] = best[r].normal;
901 stats.average_ray_distance += best[r].distance;
903 out_primitive_UUID[begin + r] = MISS_UUID;
909 std::vector<HitResult> results;
910 results.reserve(ray_queries.size());
912 if (bvh_nodes_soa.node_count == 0) {
914 results.resize(ray_queries.size());
915 stats.total_rays_cast = ray_queries.size();
916 stats.total_hits = 0;
917 stats.bvh_nodes_visited = 0;
918 stats.average_ray_distance = 0.0f;
922 stats.total_rays_cast = ray_queries.size();
923 stats.total_hits = 0;
924 stats.average_ray_distance = 0.0;
927 results.resize(ray_queries.size());
935 volatile int *
const cancel = cancel_flag;
940 RayTracingStats local_stats = {};
942#pragma omp for schedule(guided, 32)
943 for (
int i = 0; i < static_cast<int>(ray_queries.size()); ++i) {
944 if (cancel !=
nullptr && *cancel != 0) {
947 HitResult result = castRaySoATraversal(ray_queries[i], local_stats);
951 local_stats.total_hits++;
952 local_stats.average_ray_distance += result.distance;
958 stats.total_hits += local_stats.total_hits;
961 stats.average_ray_distance += local_stats.average_ray_distance;
964 stats.bvh_nodes_visited += local_stats.bvh_nodes_visited;
967 if (stats.total_hits > 0) {
968 stats.average_ray_distance /= stats.total_hits;
978using namespace helios;
983 if (bvh_nodes_soa.node_count == 0 || bvh_nodes_soa.aabb_mins.empty()) {
990 constexpr int STACK_CAPACITY = 128;
991 uint32_t node_stack[STACK_CAPACITY];
993 node_stack[stack_size++] = 0;
997 const bool use_dense_cache = (primitive_cache_dense.size() == primitive_indices.size());
999 float closest_distance = (query.max_distance > 0) ? query.max_distance : std::numeric_limits<float>::
max();
1001 while (stack_size > 0) {
1002 uint32_t node_idx = node_stack[--stack_size];
1003 stats.bvh_nodes_visited++;
1006 if (node_idx >= bvh_nodes_soa.node_count) {
1011 if (!aabbIntersectSoA(query.origin, query.direction, closest_distance, node_idx)) {
1016 if (bvh_nodes_soa.is_leaf_flags[node_idx]) {
1018 uint32_t primitive_start = bvh_nodes_soa.primitive_starts[node_idx];
1019 uint32_t primitive_count = bvh_nodes_soa.primitive_counts[node_idx];
1021 for (uint32_t i = 0; i < primitive_count; ++i) {
1022 const uint32_t slot = primitive_start + i;
1023 uint primitive_id = primitive_indices[slot];
1026 if (!query.target_UUIDs.empty()) {
1028 for (
uint target: query.target_UUIDs) {
1029 if (primitive_id == target) {
1040 HitResult primitive_result = use_dense_cache ? intersectCachedPrimitive(query.origin, query.direction, primitive_cache_dense[slot], closest_distance)
1041 : intersectPrimitiveThreadSafe(query.origin, query.direction, primitive_id, closest_distance);
1042 if (primitive_result.hit && primitive_result.distance < closest_distance) {
1043 result = primitive_result;
1044 closest_distance = primitive_result.
distance;
1050 uint32_t left_child = bvh_nodes_soa.left_children[node_idx];
1051 uint32_t right_child = bvh_nodes_soa.right_children[node_idx];
1053 const bool left_valid = (left_child != 0xFFFFFFFF && left_child < bvh_nodes_soa.node_count);
1054 const bool right_valid = (right_child != 0xFFFFFFFF && right_child < bvh_nodes_soa.node_count);
1056 if (left_valid && right_valid) {
1058 const float t_left = aabbEntryDistanceSoA(query.origin, query.direction, left_child);
1059 const float t_right = aabbEntryDistanceSoA(query.origin, query.direction, right_child);
1060 uint32_t first = left_child, second = right_child;
1061 if (t_right < t_left) {
1062 first = right_child;
1063 second = left_child;
1065 if (stack_size < STACK_CAPACITY)
1066 node_stack[stack_size++] = second;
1067 if (stack_size < STACK_CAPACITY)
1068 node_stack[stack_size++] = first;
1069 }
else if (left_valid) {
1070 if (stack_size < STACK_CAPACITY)
1071 node_stack[stack_size++] = left_child;
1072 }
else if (right_valid) {
1073 if (stack_size < STACK_CAPACITY)
1074 node_stack[stack_size++] = right_child;
1083bool CollisionDetection::aabbIntersectSoA(
const helios::vec3 &ray_origin,
const helios::vec3 &ray_direction,
float max_distance,
size_t node_index)
const {
1085 const vec3 &aabb_min = bvh_nodes_soa.aabb_mins[node_index];
1086 const vec3 &aabb_max = bvh_nodes_soa.aabb_maxs[node_index];
1090 __m128 ray_orig = _mm_set_ps(0.0f, ray_origin.
z, ray_origin.
y, ray_origin.
x);
1091 __m128 ray_dir = _mm_set_ps(0.0f, ray_direction.
z, ray_direction.
y, ray_direction.
x);
1092 __m128 aabb_min_vec = _mm_set_ps(0.0f, aabb_min.
z, aabb_min.
y, aabb_min.
x);
1093 __m128 aabb_max_vec = _mm_set_ps(0.0f, aabb_max.
z, aabb_max.
y, aabb_max.
x);
1098 constexpr float PARALLEL_EPS = 1e-8f;
1099 __m128 abs_dir = _mm_andnot_ps(_mm_set1_ps(-0.0f), ray_dir);
1100 __m128 too_small = _mm_cmplt_ps(abs_dir, _mm_set1_ps(PARALLEL_EPS));
1102 __m128 sign = _mm_and_ps(ray_dir, _mm_set1_ps(-0.0f));
1103 __m128 clamped = _mm_or_ps(sign, _mm_set1_ps(PARALLEL_EPS));
1104 __m128 safe_dir = _mm_or_ps(_mm_and_ps(too_small, clamped), _mm_andnot_ps(too_small, ray_dir));
1105 __m128 inv_dir = _mm_div_ps(_mm_set1_ps(1.0f), safe_dir);
1108 __m128 t1 = _mm_mul_ps(_mm_sub_ps(aabb_min_vec, ray_orig), inv_dir);
1109 __m128 t2 = _mm_mul_ps(_mm_sub_ps(aabb_max_vec, ray_orig), inv_dir);
1112 __m128 tmin = _mm_min_ps(t1, t2);
1113 __m128 tmax = _mm_max_ps(t1, t2);
1116 float tmin_vals[4], tmax_vals[4];
1117 _mm_store_ps(tmin_vals, tmin);
1118 _mm_store_ps(tmax_vals, tmax);
1120 float t_near = std::max({tmin_vals[0], tmin_vals[1], tmin_vals[2], 0.0f});
1121 float t_far = std::min({tmax_vals[0], tmax_vals[1], tmax_vals[2], max_distance});
1123 return t_near <= t_far;
1129 float t_near = 0.0f;
1130 float t_far = max_distance;
1132 const float origin_xyz[3] = {ray_origin.
x, ray_origin.
y, ray_origin.
z};
1133 const float dir_xyz[3] = {ray_direction.
x, ray_direction.
y, ray_direction.
z};
1134 const float min_xyz[3] = {aabb_min.
x, aabb_min.
y, aabb_min.
z};
1135 const float max_xyz[3] = {aabb_max.
x, aabb_max.
y, aabb_max.
z};
1137 constexpr float PARALLEL_EPS = 1e-8f;
1138 for (
int axis = 0; axis < 3; axis++) {
1139 if (std::abs(dir_xyz[axis]) < PARALLEL_EPS) {
1141 if (origin_xyz[axis] < min_xyz[axis] || origin_xyz[axis] > max_xyz[axis]) {
1146 const float inv = 1.0f / dir_xyz[axis];
1147 float t1 = (min_xyz[axis] - origin_xyz[axis]) * inv;
1148 float t2 = (max_xyz[axis] - origin_xyz[axis]) * inv;
1152 t_near = std::max(t_near, t1);
1153 t_far = std::min(t_far, t2);
1154 if (t_near > t_far) {
1159 return t_near <= t_far;
1163float CollisionDetection::aabbEntryDistanceSoA(
const helios::vec3 &ray_origin,
const helios::vec3 &ray_direction,
size_t node_index)
const {
1167 const vec3 &aabb_min = bvh_nodes_soa.aabb_mins[node_index];
1168 const vec3 &aabb_max = bvh_nodes_soa.aabb_maxs[node_index];
1170 const float origin_xyz[3] = {ray_origin.
x, ray_origin.
y, ray_origin.
z};
1171 const float dir_xyz[3] = {ray_direction.
x, ray_direction.
y, ray_direction.
z};
1172 const float min_xyz[3] = {aabb_min.
x, aabb_min.
y, aabb_min.
z};
1173 const float max_xyz[3] = {aabb_max.
x, aabb_max.
y, aabb_max.
z};
1175 constexpr float PARALLEL_EPS = 1e-8f;
1176 float t_near = 0.0f;
1177 float t_far = std::numeric_limits<float>::max();
1178 for (
int axis = 0; axis < 3; axis++) {
1179 if (std::abs(dir_xyz[axis]) < PARALLEL_EPS) {
1180 if (origin_xyz[axis] < min_xyz[axis] || origin_xyz[axis] > max_xyz[axis]) {
1181 return std::numeric_limits<float>::max();
1185 const float inv = 1.0f / dir_xyz[axis];
1186 float t1 = (min_xyz[axis] - origin_xyz[axis]) * inv;
1187 float t2 = (max_xyz[axis] - origin_xyz[axis]) * inv;
1191 t_near = std::max(t_near, t1);
1192 t_far = std::min(t_far, t2);
1195 return (t_near <= t_far) ? t_near : std::numeric_limits<float>::max();
1203 if (bvh_nodes.empty()) {
1208 std::stack<size_t> node_stack;
1211 float closest_distance = (query.max_distance > 0) ? query.max_distance : std::numeric_limits<float>::
max();
1213 while (!node_stack.empty()) {
1214 size_t node_idx = node_stack.top();
1217 if (node_idx >= bvh_nodes.size()) {
1218 if (printmessages) {
1219 std::cout <<
"ERROR: Invalid BVH node index " << node_idx <<
" >= " << bvh_nodes.size() <<
" nodes" << std::endl;
1225 const BVHNode &node = bvh_nodes[node_idx];
1228 if (!rayAABBIntersect(query.origin, query.direction, node.
aabb_min, node.
aabb_max)) {
1234 for (uint32_t i = 0; i < node.primitive_count; ++i) {
1235 if (node.primitive_start + i >= primitive_indices.size()) {
1236 if (printmessages) {
1237 std::cout <<
"ERROR: Invalid BVH primitive index " << (node.primitive_start + i) <<
" >= " << primitive_indices.size() <<
" primitives" << std::endl;
1243 uint primitive_id = primitive_indices[node.primitive_start + i];
1246 if (!query.target_UUIDs.empty()) {
1248 for (
uint target: query.target_UUIDs) {
1249 if (primitive_id == target) {
1259 HitResult primitive_hit = intersectPrimitive(query, primitive_id);
1260 if (primitive_hit.hit && primitive_hit.distance < closest_distance) {
1261 result = primitive_hit;
1262 closest_distance = primitive_hit.distance;
1280bool CollisionDetection::rayAABBIntersect(
const vec3 &ray_origin,
const vec3 &ray_direction,
const vec3 &aabb_min,
const vec3 &aabb_max)
const {
1282 const float EPSILON = 1e-8f;
1285 float tmax = std::numeric_limits<float>::max();
1288 if (std::abs(ray_direction.
x) > EPSILON) {
1289 float inv_dir_x = 1.0f / ray_direction.
x;
1290 float t1 = (aabb_min.
x - ray_origin.
x) * inv_dir_x;
1291 float t2 = (aabb_max.
x - ray_origin.
x) * inv_dir_x;
1293 float slab_tmin = std::min(t1, t2);
1294 float slab_tmax = std::max(t1, t2);
1296 tmin = std::max(tmin, slab_tmin);
1297 tmax = std::min(tmax, slab_tmax);
1303 if (ray_origin.
x < aabb_min.
x || ray_origin.
x > aabb_max.
x) {
1309 if (std::abs(ray_direction.
y) > EPSILON) {
1310 float inv_dir_y = 1.0f / ray_direction.
y;
1311 float t1 = (aabb_min.
y - ray_origin.
y) * inv_dir_y;
1312 float t2 = (aabb_max.
y - ray_origin.
y) * inv_dir_y;
1314 float slab_tmin = std::min(t1, t2);
1315 float slab_tmax = std::max(t1, t2);
1317 tmin = std::max(tmin, slab_tmin);
1318 tmax = std::min(tmax, slab_tmax);
1324 if (ray_origin.
y < aabb_min.
y || ray_origin.
y > aabb_max.
y) {
1330 if (std::abs(ray_direction.
z) > EPSILON) {
1331 float inv_dir_z = 1.0f / ray_direction.
z;
1332 float t1 = (aabb_min.
z - ray_origin.
z) * inv_dir_z;
1333 float t2 = (aabb_max.
z - ray_origin.
z) * inv_dir_z;
1335 float slab_tmin = std::min(t1, t2);
1336 float slab_tmax = std::max(t1, t2);
1338 tmin = std::max(tmin, slab_tmin);
1339 tmax = std::min(tmax, slab_tmax);
1345 if (ray_origin.
z < aabb_min.
z || ray_origin.
z > aabb_max.
z) {
1350 return tmin <= tmax;
1360 if (rayPrimitiveIntersection(query.origin, query.direction, primitive_id, distance)) {
1361 vec3 intersection_point = query.origin + query.direction * distance;
1365 std::vector<vec3> vertices =
context->getPrimitiveVertices(primitive_id);
1368 if (
context->primitiveTextureHasTransparencyChannel(primitive_id)) {
1369 CachedPrimitive temp_cached(type, vertices);
1370 temp_cached.transparency_mask =
context->getPrimitiveTextureTransparencyData(primitive_id);
1371 temp_cached.texture_size =
context->getPrimitiveTextureSize(primitive_id);
1372 temp_cached.uv =
context->getPrimitiveTextureUV(primitive_id);
1373 if (!isHitTexelOpaque(temp_cached, intersection_point)) {
1379 result.distance = distance;
1380 result.primitive_UUID = primitive_id;
1381 result.intersection_point = intersection_point;
1383 if (type == PRIMITIVE_TYPE_TRIANGLE && vertices.size() >= 3) {
1384 vec3 edge1 = vertices[1] - vertices[0];
1385 vec3 edge2 = vertices[2] - vertices[0];
1386 result.normal =
cross(edge1, edge2);
1387 if (result.normal.magnitude() > 1e-8f) {
1388 result.normal = result.normal / result.normal.magnitude();
1390 result.normal =
make_vec3(-query.direction.x, -query.direction.y, -query.direction.z);
1392 }
else if (type == PRIMITIVE_TYPE_PATCH && vertices.size() >= 3) {
1393 vec3 edge1 = vertices[1] - vertices[0];
1394 vec3 edge2 = vertices[2] - vertices[0];
1395 result.normal =
cross(edge1, edge2);
1396 if (result.normal.magnitude() > 1e-8f) {
1397 result.normal = result.normal / result.normal.magnitude();
1399 result.normal =
make_vec3(-query.direction.x, -query.direction.y, -query.direction.z);
1403 result.normal =
make_vec3(-query.direction.x, -query.direction.y, -query.direction.z);
1410bool CollisionDetection::isHitTexelOpaque(
const CachedPrimitive &cached,
const vec3 &hit_point)
const {
1413 if (cached.transparency_mask ==
nullptr || cached.texture_size.x <= 0 || cached.texture_size.y <= 0) {
1417 const std::vector<vec2> &uvs = cached.uv;
1423 if (cached.type == PRIMITIVE_TYPE_PATCH && cached.vertices.size() >= 4) {
1425 const vec3 e1 = cached.vertices[1] - cached.vertices[0];
1426 const vec3 e2 = cached.vertices[3] - cached.vertices[0];
1427 const vec3 d = hit_point - cached.vertices[0];
1428 const float e1_sq = e1 * e1;
1429 const float e2_sq = e2 * e2;
1430 float s_param = (e1_sq > 0.f) ? (d * e1) / e1_sq : 0.f;
1431 float t_param = (e2_sq > 0.f) ? (d * e2) / e2_sq : 0.f;
1432 s_param = std::min(std::max(s_param, 0.f), 1.f);
1433 t_param = std::min(std::max(t_param, 0.f), 1.f);
1434 if (uvs.size() == 4) {
1435 uv = (1.f - s_param) * (1.f - t_param) * uvs[0] + s_param * (1.f - t_param) * uvs[1] + s_param * t_param * uvs[2] + (1.f - s_param) * t_param * uvs[3];
1439 }
else if (cached.type == PRIMITIVE_TYPE_TRIANGLE && cached.vertices.size() >= 3 && uvs.size() == 3) {
1440 const vec3 e1 = cached.vertices[1] - cached.vertices[0];
1441 const vec3 e2 = cached.vertices[2] - cached.vertices[0];
1442 const vec3 d = hit_point - cached.vertices[0];
1443 const float dot11 = e1 * e1;
1444 const float dot12 = e1 * e2;
1445 const float dot22 = e2 * e2;
1446 const float dot1d = e1 * d;
1447 const float dot2d = e2 * d;
1448 const float denom = dot11 * dot22 - dot12 * dot12;
1449 if (std::fabs(denom) < 1e-20f) {
1452 const float inv_denom = 1.f / denom;
1453 const float beta = (dot22 * dot1d - dot12 * dot2d) * inv_denom;
1454 const float gamma = (dot11 * dot2d - dot12 * dot1d) * inv_denom;
1455 uv = uvs[0] + beta * (uvs[1] - uvs[0]) + gamma * (uvs[2] - uvs[0]);
1462 uv.
x -= std::floor(uv.
x);
1463 uv.
y -= std::floor(uv.
y);
1465 int px =
static_cast<int>(uv.
x *
static_cast<float>(cached.texture_size.x));
1466 px = std::min(std::max(px, 0), cached.texture_size.x - 1);
1468 int py =
static_cast<int>((1.f - uv.
y) *
static_cast<float>(cached.texture_size.y));
1469 py = std::min(std::max(py, 0), cached.texture_size.y - 1);
1471 const std::vector<std::vector<bool>> &mask = *cached.transparency_mask;
1472 if (py >=
static_cast<int>(mask.size()) || px >=
static_cast<int>(mask[py].size())) {
1475 return mask[py][px];
1482 auto it = primitive_cache.find(primitive_id);
1483 if (it == primitive_cache.end()) {
1489 if (rayPrimitiveIntersection(origin, direction, primitive_id, distance)) {
1490 vec3 intersection_point = origin + direction * distance;
1494 std::vector<vec3> vertices =
context->getPrimitiveVertices(primitive_id);
1498 if (
context->primitiveTextureHasTransparencyChannel(primitive_id)) {
1499 CachedPrimitive temp_cached(type, vertices);
1500 temp_cached.transparency_mask =
context->getPrimitiveTextureTransparencyData(primitive_id);
1501 temp_cached.texture_size =
context->getPrimitiveTextureSize(primitive_id);
1502 temp_cached.uv =
context->getPrimitiveTextureUV(primitive_id);
1503 if (!isHitTexelOpaque(temp_cached, intersection_point)) {
1509 result.distance = distance;
1510 result.primitive_UUID = primitive_id;
1511 result.intersection_point = intersection_point;
1513 if (type == PRIMITIVE_TYPE_TRIANGLE && vertices.size() >= 3) {
1514 vec3 edge1 = vertices[1] - vertices[0];
1515 vec3 edge2 = vertices[2] - vertices[0];
1516 result.normal =
cross(edge1, edge2);
1517 if (result.normal.magnitude() > 1e-8f) {
1518 result.normal = result.normal / result.normal.magnitude();
1520 vec3 to_origin = origin - result.intersection_point;
1521 if (result.normal * to_origin < 0) {
1522 result.normal = result.normal * -1.0f;
1525 result.normal =
make_vec3(-direction.
x, -direction.
y, -direction.
z);
1526 result.normal = result.normal / result.normal.magnitude();
1528 }
else if (type == PRIMITIVE_TYPE_PATCH && vertices.size() >= 4) {
1529 vec3 edge1 = vertices[1] - vertices[0];
1530 vec3 edge2 = vertices[2] - vertices[0];
1531 result.normal =
cross(edge1, edge2);
1532 if (result.normal.magnitude() > 1e-8f) {
1533 result.normal = result.normal / result.normal.magnitude();
1535 vec3 to_origin = origin - result.intersection_point;
1536 if (result.normal * to_origin < 0) {
1537 result.normal = result.normal * -1.0f;
1540 result.normal =
make_vec3(-direction.
x, -direction.
y, -direction.
z);
1541 result.normal = result.normal / result.normal.magnitude();
1544 result.normal =
make_vec3(-direction.
x, -direction.
y, -direction.
z);
1545 result.normal = result.normal / result.normal.magnitude();
1552 return intersectCachedPrimitive(origin, direction, it->second, max_distance);
1559 if (cached.type == PRIMITIVE_TYPE_TRIANGLE && cached.vertices.size() >= 3) {
1561 if (triangleIntersect(origin, direction, cached.vertices[0], cached.vertices[1], cached.vertices[2], distance)) {
1562 if (distance > 1e-6f && (max_distance <= 0 || distance < max_distance)) {
1563 vec3 intersection_point = origin + direction * distance;
1566 if (!isHitTexelOpaque(cached, intersection_point)) {
1571 result.distance = distance;
1572 result.primitive_UUID = cached.UUID;
1573 result.intersection_point = intersection_point;
1576 vec3 edge1 = cached.vertices[1] - cached.vertices[0];
1577 vec3 edge2 = cached.vertices[2] - cached.vertices[0];
1578 result.normal =
cross(edge1, edge2);
1579 if (result.normal.magnitude() > 1e-8f) {
1580 result.normal = result.normal / result.normal.magnitude();
1582 vec3 to_origin = origin - result.intersection_point;
1583 if (result.normal * to_origin < 0) {
1584 result.normal = result.normal * -1.0f;
1587 result.normal =
make_vec3(-direction.
x, -direction.
y, -direction.
z);
1588 result.normal = result.normal / result.normal.magnitude();
1592 }
else if (cached.type == PRIMITIVE_TYPE_PATCH && cached.vertices.size() >= 4) {
1594 if (patchIntersect(origin, direction, cached.vertices[0], cached.vertices[1], cached.vertices[2], cached.vertices[3], distance)) {
1595 if (distance > 1e-6f && (max_distance <= 0 || distance < max_distance)) {
1596 vec3 intersection_point = origin + direction * distance;
1599 if (!isHitTexelOpaque(cached, intersection_point)) {
1604 result.distance = distance;
1605 result.primitive_UUID = cached.UUID;
1606 result.intersection_point = intersection_point;
1609 vec3 edge1 = cached.vertices[1] - cached.vertices[0];
1610 vec3 edge2 = cached.vertices[2] - cached.vertices[0];
1611 result.normal =
cross(edge1, edge2);
1612 if (result.normal.magnitude() > 1e-8f) {
1613 result.normal = result.normal / result.normal.magnitude();
1615 vec3 to_origin = origin - result.intersection_point;
1616 if (result.normal * to_origin < 0) {
1617 result.normal = result.normal * -1.0f;
1620 result.normal =
make_vec3(-direction.
x, -direction.
y, -direction.
z);
1621 result.normal = result.normal / result.normal.magnitude();
1625 }
else if (cached.type == PRIMITIVE_TYPE_VOXEL && cached.vertices.size() == 8) {
1628 vec3 aabb_min = cached.vertices[0];
1629 vec3 aabb_max = cached.vertices[0];
1631 for (
int i = 1; i < 8; i++) {
1632 aabb_min.
x = std::min(aabb_min.
x, cached.vertices[i].x);
1633 aabb_min.
y = std::min(aabb_min.
y, cached.vertices[i].y);
1634 aabb_min.
z = std::min(aabb_min.
z, cached.vertices[i].z);
1635 aabb_max.
x = std::max(aabb_max.
x, cached.vertices[i].x);
1636 aabb_max.
y = std::max(aabb_max.
y, cached.vertices[i].y);
1637 aabb_max.
z = std::max(aabb_max.
z, cached.vertices[i].z);
1641 float t_near = -std::numeric_limits<float>::max();
1642 float t_far = std::numeric_limits<float>::max();
1645 for (
int axis = 0; axis < 3; axis++) {
1646 float ray_dir_component = (axis == 0) ? direction.
x : (axis == 1) ? direction.y : direction.z;
1647 float ray_orig_component = (axis == 0) ? origin.
x : (axis == 1) ? origin.y : origin.z;
1648 float aabb_min_component = (axis == 0) ? aabb_min.
x : (axis == 1) ? aabb_min.y : aabb_min.z;
1649 float aabb_max_component = (axis == 0) ? aabb_max.
x : (axis == 1) ? aabb_max.y : aabb_max.z;
1651 if (std::abs(ray_dir_component) < 1e-8f) {
1653 if (ray_orig_component < aabb_min_component || ray_orig_component > aabb_max_component) {
1658 float t1 = (aabb_min_component - ray_orig_component) / ray_dir_component;
1659 float t2 = (aabb_max_component - ray_orig_component) / ray_dir_component;
1667 t_near = std::max(t_near, t1);
1668 t_far = std::min(t_far, t2);
1671 if (t_near > t_far) {
1678 if (t_far >= 0.0f) {
1680 float intersection_distance = (t_near >= 1e-6f) ? t_near : t_far;
1681 if (intersection_distance >= 1e-6f && (max_distance <= 0 || intersection_distance < max_distance)) {
1683 result.distance = intersection_distance;
1684 result.primitive_UUID = cached.UUID;
1685 result.intersection_point = origin + direction * intersection_distance;
1689 vec3 hit_point = result.intersection_point;
1690 vec3 box_center = (aabb_min + aabb_max) * 0.5f;
1691 vec3 box_extent = (aabb_max - aabb_min) * 0.5f;
1694 vec3 local_hit = hit_point - box_center;
1695 vec3 abs_local_hit =
make_vec3(std::abs(local_hit.
x), std::abs(local_hit.
y), std::abs(local_hit.
z));
1698 float rel_x = abs_local_hit.
x / box_extent.
x;
1699 float rel_y = abs_local_hit.
y / box_extent.
y;
1700 float rel_z = abs_local_hit.
z / box_extent.
z;
1702 if (rel_x >= rel_y && rel_x >= rel_z) {
1704 result.normal =
make_vec3((local_hit.
x > 0) ? 1.0f : -1.0f, 0.0f, 0.0f);
1705 }
else if (rel_y >= rel_z) {
1707 result.normal =
make_vec3(0.0f, (local_hit.
y > 0) ? 1.0f : -1.0f, 0.0f);
1710 result.normal =
make_vec3(0.0f, 0.0f, (local_hit.
z > 0) ? 1.0f : -1.0f);
1720void CollisionDetection::buildPrimitiveCache() {
1721 primitive_cache.clear();
1724 std::vector<uint> all_primitives =
context->getAllUUIDs();
1727 for (
uint primitive_id: all_primitives) {
1728 if (
context->doesPrimitiveExist(primitive_id)) {
1731 std::vector<vec3> vertices =
context->getPrimitiveVertices(primitive_id);
1733 CachedPrimitive cached(type, vertices);
1734 cached.UUID = primitive_id;
1741 if (
context->primitiveTextureHasTransparencyChannel(primitive_id)) {
1742 cached.transparency_mask =
context->getPrimitiveTextureTransparencyData(primitive_id);
1743 cached.texture_size =
context->getPrimitiveTextureSize(primitive_id);
1744 cached.uv =
context->getPrimitiveTextureUV(primitive_id);
1747 primitive_cache[primitive_id] = std::move(cached);
1748 }
catch (
const std::exception &e) {
1751 if (printmessages) {
1752 std::cout <<
"Warning: Skipping primitive " << primitive_id <<
" in cache build (not accessible: " << e.what() <<
")" << std::endl;
1760 rebuildDensePrimitiveCache();
1763void CollisionDetection::rebuildDensePrimitiveCache() {
1769 primitive_cache_dense.assign(primitive_indices.size(), CachedPrimitive());
1770 for (
size_t i = 0; i < primitive_indices.size(); i++) {
1771 auto it = primitive_cache.find(primitive_indices[i]);
1772 if (it != primitive_cache.end()) {
1773 primitive_cache_dense[i] = it->second;
1778void CollisionDetection::ensurePrimitiveCacheCurrent() {
1781 if (primitive_cache.empty()) {
1782 buildPrimitiveCache();
1788 if (primitive_cache_dense.size() != primitive_indices.size()) {
1789 rebuildDensePrimitiveCache();
1793bool CollisionDetection::triangleIntersect(
const vec3 &origin,
const vec3 &direction,
const vec3 &v0,
const vec3 &v1,
const vec3 &v2,
float &distance)
const {
1796 const float EPSILON = 1e-5f;
1799 float edge1_x = v1.
x - v0.
x, edge1_y = v1.
y - v0.
y, edge1_z = v1.
z - v0.
z;
1800 float edge2_x = v2.
x - v0.
x, edge2_y = v2.
y - v0.
y, edge2_z = v2.
z - v0.
z;
1803 float h_x = direction.
y * edge2_z - direction.
z * edge2_y;
1804 float h_y = direction.
z * edge2_x - direction.
x * edge2_z;
1805 float h_z = direction.
x * edge2_y - direction.
y * edge2_x;
1808 float a = edge1_x * h_x + edge1_y * h_y + edge1_z * h_z;
1810 if (a > -EPSILON && a < EPSILON) {
1817 float s_x = origin.
x - v0.
x, s_y = origin.
y - v0.
y, s_z = origin.
z - v0.
z;
1820 float u = f * (s_x * h_x + s_y * h_y + s_z * h_z);
1822 if (u < -EPSILON || u > 1.0f + EPSILON) {
1827 float q_x = s_y * edge1_z - s_z * edge1_y;
1828 float q_y = s_z * edge1_x - s_x * edge1_z;
1829 float q_z = s_x * edge1_y - s_y * edge1_x;
1832 float v = f * (direction.
x * q_x + direction.
y * q_y + direction.
z * q_z);
1834 if (v < -EPSILON || u + v > 1.0f + EPSILON) {
1839 float t = f * (edge2_x * q_x + edge2_y * q_y + edge2_z * q_z);
1849bool CollisionDetection::patchIntersect(
const vec3 &origin,
const vec3 &direction,
const vec3 &v0,
const vec3 &v1,
const vec3 &v2,
const vec3 &v3,
float &distance)
const {
1851 const float EPSILON = 1e-8f;
1862 float denom = direction * normal;
1863 if (std::abs(denom) > EPSILON) {
1864 float t = (anchor - origin) * normal / denom;
1866 if (t > EPSILON && t < 1e8f) {
1868 vec3 p = origin + direction * t;
1869 vec3 d = p - anchor;
1872 float ddota = d * a;
1873 float ddotb = d * b;
1876 if (ddota >= -EPSILON && ddota <= (a * a) + EPSILON && ddotb >= -EPSILON && ddotb <= (b * b) + EPSILON) {
1886#ifdef HELIOS_CUDA_AVAILABLE
1887#include <vector_types.h>
1895std::vector<CollisionDetection::HitResult> CollisionDetection::castRaysGPU(
const std::vector<RayQuery> &ray_queries, RayTracingStats &stats) {
1896 std::vector<HitResult> results;
1897 results.resize(ray_queries.size());
1899 if (ray_queries.empty() || !gpu_acceleration_enabled) {
1905 if (bvh_nodes.empty()) {
1907 if (bvh_nodes.empty()) {
1908 helios_runtime_error(
"ERROR: BVH construction failed - no geometry available for ray tracing. Ensure primitives are properly added to the collision detection system.");
1913 if (d_bvh_nodes ==
nullptr || d_primitive_vertices ==
nullptr) {
1917 if (!gpu_acceleration_enabled || d_bvh_nodes ==
nullptr || d_primitive_vertices ==
nullptr || primitive_indices.empty()) {
1923 const size_t num_rays = ray_queries.size();
1924 std::vector<float> ray_origins(num_rays * 3);
1925 std::vector<float> ray_directions(num_rays * 3);
1926 std::vector<float> ray_max_distances(num_rays);
1927 for (
size_t i = 0; i < num_rays; i++) {
1928 vec3 dir = ray_queries[i].direction;
1933 ray_origins[i * 3] = ray_queries[i].origin.
x;
1934 ray_origins[i * 3 + 1] = ray_queries[i].origin.y;
1935 ray_origins[i * 3 + 2] = ray_queries[i].origin.z;
1936 ray_directions[i * 3] = dir.
x;
1937 ray_directions[i * 3 + 1] = dir.
y;
1938 ray_directions[i * 3 + 2] = dir.
z;
1939 ray_max_distances[i] = (ray_queries[i].max_distance > 0) ? ray_queries[i].max_distance : std::numeric_limits<float>::
max();
1942 std::vector<float> hit_distances(num_rays);
1943 std::vector<unsigned int> hit_primitive_ids(num_rays);
1944 std::vector<unsigned int> hit_counts(num_rays);
1945 std::vector<float> hit_normals(num_rays * 3);
1949 launchRaysOnResidentScene(d_bvh_nodes, d_gpu_node_count, d_primitive_indices, d_gpu_primitive_count, d_primitive_types, (float3 *) d_primitive_vertices, d_vertex_offsets, (
const unsigned char *) d_mask_data, d_mask_offsets, d_mask_sizes,
1950 d_mask_IDs, (
const float *) d_uv_data, d_uv_IDs, d_gpu_total_vertex_count, ray_origins.data(), ray_directions.data(), ray_max_distances.data(), 0.0f,
static_cast<int>(num_rays),
1951 hit_distances.data(), hit_primitive_ids.data(), hit_counts.data(), hit_normals.data(),
true);
1953 size_t hit_count = 0;
1954 for (
size_t i = 0; i < num_rays; i++) {
1955 const float max_d = (ray_queries[i].max_distance > 0) ? ray_queries[i].max_distance : std::numeric_limits<float>::
max();
1956 if (hit_counts[i] > 0 && hit_distances[i] <= max_d) {
1957 results[i].hit =
true;
1958 results[i].primitive_UUID = hit_primitive_ids[i];
1959 results[i].distance = hit_distances[i];
1960 results[i].intersection_point = ray_queries[i].origin + ray_queries[i].direction * hit_distances[i];
1961 results[i].normal =
make_vec3(hit_normals[i * 3], hit_normals[i * 3 + 1], hit_normals[i * 3 + 2]);
1964 results[i].hit =
false;
1965 results[i].primitive_UUID = 0;
1966 results[i].distance = std::numeric_limits<float>::max();
1970 stats.total_rays_cast = num_rays;
1971 stats.total_hits = hit_count;
1980uint32_t CollisionDetection::rayAABBIntersectSIMD(
const vec3 *ray_origins,
const vec3 *ray_directions,
const vec3 *aabb_mins,
const vec3 *aabb_maxs,
float *t_mins,
float *t_maxs,
int count) {
1984 uint32_t hit_mask = 0;
1986 for (
int i = 0; i < 8; i += 8) {
1988 __m256 orig_x = _mm256_set_ps(ray_origins[i + 7].x, ray_origins[i + 6].x, ray_origins[i + 5].x, ray_origins[i + 4].x, ray_origins[i + 3].x, ray_origins[i + 2].x, ray_origins[i + 1].x, ray_origins[i + 0].x);
1989 __m256 orig_y = _mm256_set_ps(ray_origins[i + 7].y, ray_origins[i + 6].y, ray_origins[i + 5].y, ray_origins[i + 4].y, ray_origins[i + 3].y, ray_origins[i + 2].y, ray_origins[i + 1].y, ray_origins[i + 0].y);
1990 __m256 orig_z = _mm256_set_ps(ray_origins[i + 7].z, ray_origins[i + 6].z, ray_origins[i + 5].z, ray_origins[i + 4].z, ray_origins[i + 3].z, ray_origins[i + 2].z, ray_origins[i + 1].z, ray_origins[i + 0].z);
1993 __m256 dir_x = _mm256_set_ps(ray_directions[i + 7].x, ray_directions[i + 6].x, ray_directions[i + 5].x, ray_directions[i + 4].x, ray_directions[i + 3].x, ray_directions[i + 2].x, ray_directions[i + 1].x, ray_directions[i + 0].x);
1994 __m256 dir_y = _mm256_set_ps(ray_directions[i + 7].y, ray_directions[i + 6].y, ray_directions[i + 5].y, ray_directions[i + 4].y, ray_directions[i + 3].y, ray_directions[i + 2].y, ray_directions[i + 1].y, ray_directions[i + 0].y);
1995 __m256 dir_z = _mm256_set_ps(ray_directions[i + 7].z, ray_directions[i + 6].z, ray_directions[i + 5].z, ray_directions[i + 4].z, ray_directions[i + 3].z, ray_directions[i + 2].z, ray_directions[i + 1].z, ray_directions[i + 0].z);
1998 __m256 aabb_min_x = _mm256_set_ps(aabb_mins[i + 7].x, aabb_mins[i + 6].x, aabb_mins[i + 5].x, aabb_mins[i + 4].x, aabb_mins[i + 3].x, aabb_mins[i + 2].x, aabb_mins[i + 1].x, aabb_mins[i + 0].x);
1999 __m256 aabb_min_y = _mm256_set_ps(aabb_mins[i + 7].y, aabb_mins[i + 6].y, aabb_mins[i + 5].y, aabb_mins[i + 4].y, aabb_mins[i + 3].y, aabb_mins[i + 2].y, aabb_mins[i + 1].y, aabb_mins[i + 0].y);
2000 __m256 aabb_min_z = _mm256_set_ps(aabb_mins[i + 7].z, aabb_mins[i + 6].z, aabb_mins[i + 5].z, aabb_mins[i + 4].z, aabb_mins[i + 3].z, aabb_mins[i + 2].z, aabb_mins[i + 1].z, aabb_mins[i + 0].z);
2003 __m256 aabb_max_x = _mm256_set_ps(aabb_maxs[i + 7].x, aabb_maxs[i + 6].x, aabb_maxs[i + 5].x, aabb_maxs[i + 4].x, aabb_maxs[i + 3].x, aabb_maxs[i + 2].x, aabb_maxs[i + 1].x, aabb_maxs[i + 0].x);
2004 __m256 aabb_max_y = _mm256_set_ps(aabb_maxs[i + 7].y, aabb_maxs[i + 6].y, aabb_maxs[i + 5].y, aabb_maxs[i + 4].y, aabb_maxs[i + 3].y, aabb_maxs[i + 2].y, aabb_maxs[i + 1].y, aabb_maxs[i + 0].y);
2005 __m256 aabb_max_z = _mm256_set_ps(aabb_maxs[i + 7].z, aabb_maxs[i + 6].z, aabb_maxs[i + 5].z, aabb_maxs[i + 4].z, aabb_maxs[i + 3].z, aabb_maxs[i + 2].z, aabb_maxs[i + 1].z, aabb_maxs[i + 0].z);
2008 __m256 inv_dir_x = _mm256_div_ps(_mm256_set1_ps(1.0f), dir_x);
2009 __m256 inv_dir_y = _mm256_div_ps(_mm256_set1_ps(1.0f), dir_y);
2010 __m256 inv_dir_z = _mm256_div_ps(_mm256_set1_ps(1.0f), dir_z);
2013 __m256 t1_x = _mm256_mul_ps(_mm256_sub_ps(aabb_min_x, orig_x), inv_dir_x);
2014 __m256 t2_x = _mm256_mul_ps(_mm256_sub_ps(aabb_max_x, orig_x), inv_dir_x);
2015 __m256 tmin_x = _mm256_min_ps(t1_x, t2_x);
2016 __m256 tmax_x = _mm256_max_ps(t1_x, t2_x);
2019 __m256 t1_y = _mm256_mul_ps(_mm256_sub_ps(aabb_min_y, orig_y), inv_dir_y);
2020 __m256 t2_y = _mm256_mul_ps(_mm256_sub_ps(aabb_max_y, orig_y), inv_dir_y);
2021 __m256 tmin_y = _mm256_min_ps(t1_y, t2_y);
2022 __m256 tmax_y = _mm256_max_ps(t1_y, t2_y);
2025 __m256 t1_z = _mm256_mul_ps(_mm256_sub_ps(aabb_min_z, orig_z), inv_dir_z);
2026 __m256 t2_z = _mm256_mul_ps(_mm256_sub_ps(aabb_max_z, orig_z), inv_dir_z);
2027 __m256 tmin_z = _mm256_min_ps(t1_z, t2_z);
2028 __m256 tmax_z = _mm256_max_ps(t1_z, t2_z);
2031 __m256 t_min_final = _mm256_max_ps(_mm256_max_ps(tmin_x, tmin_y), tmin_z);
2032 __m256 t_max_final = _mm256_min_ps(_mm256_min_ps(tmax_x, tmax_y), tmax_z);
2035 _mm256_store_ps(&t_mins[i], t_min_final);
2036 _mm256_store_ps(&t_maxs[i], t_max_final);
2039 __m256 zero = _mm256_set1_ps(0.0f);
2040 __m256 hits = _mm256_and_ps(_mm256_cmp_ps(t_max_final, zero, _CMP_GE_OS), _mm256_cmp_ps(t_min_final, t_max_final, _CMP_LE_OS));
2043 hit_mask |= _mm256_movemask_ps(hits);
2053 uint32_t hit_mask = 0;
2055 for (
int i = 0; i < 4; i += 4) {
2057 __m128 orig_x = _mm_set_ps(ray_origins[i + 3].x, ray_origins[i + 2].x, ray_origins[i + 1].x, ray_origins[i + 0].x);
2058 __m128 orig_y = _mm_set_ps(ray_origins[i + 3].y, ray_origins[i + 2].y, ray_origins[i + 1].y, ray_origins[i + 0].y);
2059 __m128 orig_z = _mm_set_ps(ray_origins[i + 3].z, ray_origins[i + 2].z, ray_origins[i + 1].z, ray_origins[i + 0].z);
2062 __m128 dir_x = _mm_set_ps(ray_directions[i + 3].x, ray_directions[i + 2].x, ray_directions[i + 1].x, ray_directions[i + 0].x);
2063 __m128 dir_y = _mm_set_ps(ray_directions[i + 3].y, ray_directions[i + 2].y, ray_directions[i + 1].y, ray_directions[i + 0].y);
2064 __m128 dir_z = _mm_set_ps(ray_directions[i + 3].z, ray_directions[i + 2].z, ray_directions[i + 1].z, ray_directions[i + 0].z);
2067 __m128 aabb_min_x = _mm_set_ps(aabb_mins[i + 3].x, aabb_mins[i + 2].x, aabb_mins[i + 1].x, aabb_mins[i + 0].x);
2068 __m128 aabb_min_y = _mm_set_ps(aabb_mins[i + 3].y, aabb_mins[i + 2].y, aabb_mins[i + 1].y, aabb_mins[i + 0].y);
2069 __m128 aabb_min_z = _mm_set_ps(aabb_mins[i + 3].z, aabb_mins[i + 2].z, aabb_mins[i + 1].z, aabb_mins[i + 0].z);
2072 __m128 aabb_max_x = _mm_set_ps(aabb_maxs[i + 3].x, aabb_maxs[i + 2].x, aabb_maxs[i + 1].x, aabb_maxs[i + 0].x);
2073 __m128 aabb_max_y = _mm_set_ps(aabb_maxs[i + 3].y, aabb_maxs[i + 2].y, aabb_maxs[i + 1].y, aabb_maxs[i + 0].y);
2074 __m128 aabb_max_z = _mm_set_ps(aabb_maxs[i + 3].z, aabb_maxs[i + 2].z, aabb_maxs[i + 1].z, aabb_maxs[i + 0].z);
2077 __m128 inv_dir_x = _mm_div_ps(_mm_set1_ps(1.0f), dir_x);
2078 __m128 inv_dir_y = _mm_div_ps(_mm_set1_ps(1.0f), dir_y);
2079 __m128 inv_dir_z = _mm_div_ps(_mm_set1_ps(1.0f), dir_z);
2082 __m128 t1_x = _mm_mul_ps(_mm_sub_ps(aabb_min_x, orig_x), inv_dir_x);
2083 __m128 t2_x = _mm_mul_ps(_mm_sub_ps(aabb_max_x, orig_x), inv_dir_x);
2084 __m128 tmin_x = _mm_min_ps(t1_x, t2_x);
2085 __m128 tmax_x = _mm_max_ps(t1_x, t2_x);
2088 __m128 t1_y = _mm_mul_ps(_mm_sub_ps(aabb_min_y, orig_y), inv_dir_y);
2089 __m128 t2_y = _mm_mul_ps(_mm_sub_ps(aabb_max_y, orig_y), inv_dir_y);
2090 __m128 tmin_y = _mm_min_ps(t1_y, t2_y);
2091 __m128 tmax_y = _mm_max_ps(t1_y, t2_y);
2094 __m128 t1_z = _mm_mul_ps(_mm_sub_ps(aabb_min_z, orig_z), inv_dir_z);
2095 __m128 t2_z = _mm_mul_ps(_mm_sub_ps(aabb_max_z, orig_z), inv_dir_z);
2096 __m128 tmin_z = _mm_min_ps(t1_z, t2_z);
2097 __m128 tmax_z = _mm_max_ps(t1_z, t2_z);
2100 __m128 t_min_final = _mm_max_ps(_mm_max_ps(tmin_x, tmin_y), tmin_z);
2101 __m128 t_max_final = _mm_min_ps(_mm_min_ps(tmax_x, tmax_y), tmax_z);
2104 _mm_store_ps(&t_mins[i], t_min_final);
2105 _mm_store_ps(&t_maxs[i], t_max_final);
2108 __m128 zero = _mm_set1_ps(0.0f);
2109 __m128 hits = _mm_and_ps(_mm_cmpge_ps(t_max_final, zero), _mm_cmple_ps(t_min_final, t_max_final));
2112 hit_mask |= _mm_movemask_ps(hits);
2120 uint32_t hit_mask = 0;
2121 for (
int i = 0; i < count; ++i) {
2122 if (rayAABBIntersect(ray_origins[i], ray_directions[i], aabb_mins[i], aabb_maxs[i], t_mins[i], t_maxs[i])) {
2123 hit_mask |= (1 << i);
2129void CollisionDetection::traverseBVHSIMD(
const vec3 *ray_origins,
const vec3 *ray_directions,
int count, HitResult *results) {
2130 if (bvh_nodes.empty()) {
2132 for (
int i = 0; i < count; ++i) {
2133 results[i] = HitResult();
2139 int simd_batch_size = 1;
2141 simd_batch_size = 8;
2142#elif defined(__SSE4_1__)
2143 simd_batch_size = 4;
2147 for (
int batch_start = 0; batch_start < count; batch_start += simd_batch_size) {
2148 int batch_count = std::min(simd_batch_size, count - batch_start);
2151 for (
int i = 0; i < batch_count; ++i) {
2152 results[batch_start + i] = HitResult();
2155 if (batch_count >= simd_batch_size && simd_batch_size > 1) {
2157 traverseBVHSIMDImpl(&ray_origins[batch_start], &ray_directions[batch_start], batch_count, &results[batch_start]);
2160 for (
int i = 0; i < batch_count; ++i) {
2161 int ray_idx = batch_start + i;
2162 results[ray_idx] =
castRay(RayQuery(ray_origins[ray_idx], ray_directions[ray_idx]));
2168void CollisionDetection::traverseBVHSIMDImpl(
const vec3 *ray_origins,
const vec3 *ray_directions,
int count, HitResult *results) {
2169 const size_t MAX_STACK_SIZE = 64;
2172 alignas(32) uint32_t node_stacks[8][MAX_STACK_SIZE];
2173 alignas(32) uint32_t stack_tops[8] = {0};
2174 alignas(32)
float closest_distances[8];
2175 alignas(32)
bool ray_active[8];
2178 for (
int i = 0; i < count; ++i) {
2179 node_stacks[i][0] = 0;
2181 closest_distances[i] = std::numeric_limits<float>::max();
2182 ray_active[i] =
true;
2183 results[i] = HitResult();
2188 bool any_active =
false;
2189 for (
int i = 0; i < count; ++i) {
2190 if (ray_active[i] && stack_tops[i] > 0) {
2199 alignas(32)
vec3 test_aabb_mins[8];
2200 alignas(32)
vec3 test_aabb_maxs[8];
2201 alignas(32) uint32_t test_node_indices[8];
2202 alignas(32)
int test_ray_indices[8];
2205 for (
int i = 0; i < count; ++i) {
2206 if (ray_active[i] && stack_tops[i] > 0) {
2207 uint32_t node_idx = node_stacks[i][--stack_tops[i]];
2208 const BVHNode &node = bvh_nodes[node_idx];
2210 test_aabb_mins[test_count] = node.
aabb_min;
2211 test_aabb_maxs[test_count] = node.
aabb_max;
2212 test_node_indices[test_count] = node_idx;
2213 test_ray_indices[test_count] = i;
2216 if (test_count == count)
2221 if (test_count == 0)
2225 alignas(32)
vec3 batch_origins[8];
2226 alignas(32)
vec3 batch_directions[8];
2227 alignas(32)
float t_mins[8];
2228 alignas(32)
float t_maxs[8];
2230 for (
int i = 0; i < test_count; ++i) {
2231 int ray_idx = test_ray_indices[i];
2232 batch_origins[i] = ray_origins[ray_idx];
2233 batch_directions[i] = ray_directions[ray_idx];
2237 uint32_t hit_mask = rayAABBIntersectSIMD(batch_origins, batch_directions, test_aabb_mins, test_aabb_maxs, t_mins, t_maxs, test_count);
2240 for (
int i = 0; i < test_count; ++i) {
2241 if (!(hit_mask & (1 << i)))
2244 int ray_idx = test_ray_indices[i];
2245 uint32_t node_idx = test_node_indices[i];
2246 const BVHNode &node = bvh_nodes[node_idx];
2248 if (t_mins[i] > closest_distances[ray_idx])
2253 for (uint32_t prim_idx = node.primitive_start; prim_idx < node.primitive_start + node.primitive_count; ++prim_idx) {
2255 uint32_t primitive_id = primitive_indices[prim_idx];
2258 HitResult prim_result = intersectPrimitiveThreadSafe(batch_origins[i], batch_directions[i], primitive_id, closest_distances[ray_idx]);
2259 if (prim_result.hit && prim_result.distance < closest_distances[ray_idx]) {
2260 closest_distances[ray_idx] = prim_result.distance;
2261 results[ray_idx] = prim_result;
2266 if (stack_tops[ray_idx] < MAX_STACK_SIZE - 2) {
2267 node_stacks[ray_idx][stack_tops[ray_idx]++] = node.
left_child;
2268 node_stacks[ray_idx][stack_tops[ray_idx]++] = node.
right_child;
2271 ray_active[ray_idx] =
false;
2284 const float EPSILON = 1e-8f;
2287 float t_min_x = (aabb_min.
x - origin.
x) / direction.
x;
2288 float t_max_x = (aabb_max.
x - origin.
x) / direction.
x;
2291 if (direction.
x < 0.0f) {
2292 float temp = t_min_x;
2297 float t_min_y = (aabb_min.
y - origin.
y) / direction.
y;
2298 float t_max_y = (aabb_max.
y - origin.
y) / direction.
y;
2300 if (direction.
y < 0.0f) {
2301 float temp = t_min_y;
2307 float t_min = std::max(t_min_x, t_min_y);
2308 float t_max = std::min(t_max_x, t_max_y);
2310 if (t_min > t_max) {
2314 float t_min_z = (aabb_min.
z - origin.
z) / direction.
z;
2315 float t_max_z = (aabb_max.
z - origin.
z) / direction.
z;
2317 if (direction.
z < 0.0f) {
2318 float temp = t_min_z;
2324 t_min = std::max(t_min, t_min_z);
2325 t_max = std::min(t_max, t_max_z);
2327 if (t_min > t_max || t_max < EPSILON) {
2332 distance = (t_min > EPSILON) ? t_min : t_max;
2334 return distance > EPSILON;