1.3.77
 
Loading...
Searching...
No Matches
CollisionDetection.cu
Go to the documentation of this file.
1
16#include <cstdio>
17#include <cuda.h>
18#include <cuda_runtime.h>
19#include <device_launch_parameters.h>
20#include <string>
21#include <vector>
22
23#include "helios_vector_types.h"
24
25// Forward declaration to avoid pulling the full helios global header (and its template-heavy transitive includes)
26// into the CUDA translation unit. Defined in core/src/global.cpp.
27namespace helios {
28 void helios_runtime_error(const std::string &error_message);
29}
30
34#define HELIOS_CUDA_CHECK(call) \
35 do { \
36 cudaError_t _helios_cuda_err = (call); \
37 if (_helios_cuda_err != cudaSuccess) { \
38 helios::helios_runtime_error(std::string("CUDA error (") + #call + "): " + cudaGetErrorString(_helios_cuda_err)); \
39 } \
40 } while (0)
41
48struct GPUBVHNode {
49 float3 aabb_min;
50 float3 aabb_max;
51 unsigned int left_child;
52 unsigned int right_child;
53 unsigned int primitive_start;
54 unsigned int primitive_count;
55 unsigned int is_leaf;
56 unsigned int padding;
57};
58
64#define BVH_TRAVERSAL_STACK_CAPACITY 128
65
69__device__ unsigned int d_bvh_stack_overflow = 0;
70
78 // Hot data: frequently accessed during traversal (separate arrays for coalescing)
79 float3 *aabb_mins;
80 float3 *aabb_maxs;
81 uint32_t *left_children;
82 uint32_t *right_children;
83
84 // Cold data: accessed less frequently
85 uint32_t *primitive_starts;
86 uint32_t *primitive_counts;
87 uint8_t *is_leaf_flags;
88
89 size_t node_count;
90};
91
100__device__ bool d_aabbIntersect(const float3 &min1, const float3 &max1, const float3 &min2, const float3 &max2) {
101 return (min1.x <= max2.x && max1.x >= min2.x) && (min1.y <= max2.y && max1.y >= min2.y) && (min1.z <= max2.z && max1.z >= min2.z);
102}
103
107__device__ __forceinline__ float3 cross(const float3 &a, const float3 &b) {
108 return make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
109}
110
111__device__ __forceinline__ float dot(const float3 &a, const float3 &b) {
112 return a.x * b.x + a.y * b.y + a.z * b.z;
113}
114
115__device__ __forceinline__ float3 normalize(const float3 &v) {
116 float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
117 if (len > 1e-8f) {
118 return make_float3(v.x / len, v.y / len, v.z / len);
119 }
120 return make_float3(0.0f, 0.0f, 1.0f); // Default up vector
121}
122
123__device__ __forceinline__ float3 operator+(const float3 &a, const float3 &b) {
124 return make_float3(a.x + b.x, a.y + b.y, a.z + b.z);
125}
126
127__device__ __forceinline__ float3 operator-(const float3 &a, const float3 &b) {
128 return make_float3(a.x - b.x, a.y - b.y, a.z - b.z);
129}
130
131__device__ __forceinline__ float3 operator*(const float3 &a, float scalar) {
132 return make_float3(a.x * scalar, a.y * scalar, a.z * scalar);
133}
134
146__device__ __forceinline__ bool rayTriangleIntersect(const float3 &ray_origin, const float3 &ray_direction, const float3 &v0, const float3 &v1, const float3 &v2, float max_distance, float &hit_distance) {
147 const float EPSILON = 1e-5f; // Match CPU tolerance for consistent GPU/CPU behavior
148
149 // Find vectors for two edges sharing v0
150 float3 edge1 = make_float3(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);
151 float3 edge2 = make_float3(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);
152
153 // Begin calculating determinant - also used to calculate u parameter
154 float3 h = make_float3(ray_direction.y * edge2.z - ray_direction.z * edge2.y, ray_direction.z * edge2.x - ray_direction.x * edge2.z, ray_direction.x * edge2.y - ray_direction.y * edge2.x);
155
156 // If determinant is near zero, ray lies in plane of triangle
157 float a = edge1.x * h.x + edge1.y * h.y + edge1.z * h.z;
158 if (a > -EPSILON && a < EPSILON) {
159 return false; // This ray is parallel to this triangle.
160 }
161
162 float f = 1.0f / a;
163 float3 s = make_float3(ray_origin.x - v0.x, ray_origin.y - v0.y, ray_origin.z - v0.z);
164 float u = f * (s.x * h.x + s.y * h.y + s.z * h.z);
165
166 if (u < -EPSILON || u > 1.0f + EPSILON) {
167 return false;
168 }
169
170 float3 q = make_float3(s.y * edge1.z - s.z * edge1.y, s.z * edge1.x - s.x * edge1.z, s.x * edge1.y - s.y * edge1.x);
171
172 float v = f * (ray_direction.x * q.x + ray_direction.y * q.y + ray_direction.z * q.z);
173
174 if (v < -EPSILON || u + v > 1.0f + EPSILON) {
175 return false;
176 }
177
178 // At this stage we can compute t to find out where the intersection point is on the line.
179 float t = f * (edge2.x * q.x + edge2.y * q.y + edge2.z * q.z);
180
181 // Match CPU behavior - don't check max_distance here (checked by caller)
182 if (t > EPSILON) { // ray intersection
183 hit_distance = t;
184 return true;
185 } else { // This means that there is a line intersection but not a ray intersection.
186 return false;
187 }
188}
189
204__device__ __forceinline__ float3 safeRayInvDir(const float3 &dir) {
205 constexpr float PARALLEL_EPS = 1e-8f;
206 float dx = (fabsf(dir.x) < PARALLEL_EPS) ? copysignf(PARALLEL_EPS, dir.x) : dir.x;
207 float dy = (fabsf(dir.y) < PARALLEL_EPS) ? copysignf(PARALLEL_EPS, dir.y) : dir.y;
208 float dz = (fabsf(dir.z) < PARALLEL_EPS) ? copysignf(PARALLEL_EPS, dir.z) : dir.z;
209 return make_float3(1.0f / dx, 1.0f / dy, 1.0f / dz);
210}
211
212__device__ __forceinline__ bool warpRayAABBIntersect(const float3 &ray_origin, const float3 &ray_dir, const float3 &aabb_min, const float3 &aabb_max, float max_dist) {
213 // Optimized ray-AABB intersection using slab method
214 // Compute intersection distances for each axis (sign-preserving reciprocal avoids 0*inf=NaN for axis-aligned rays).
215 float3 inv_dir = safeRayInvDir(ray_dir);
216
217 float3 t_min = make_float3((aabb_min.x - ray_origin.x) * inv_dir.x, (aabb_min.y - ray_origin.y) * inv_dir.y, (aabb_min.z - ray_origin.z) * inv_dir.z);
218
219 float3 t_max = make_float3((aabb_max.x - ray_origin.x) * inv_dir.x, (aabb_max.y - ray_origin.y) * inv_dir.y, (aabb_max.z - ray_origin.z) * inv_dir.z);
220
221 // Handle negative ray directions
222 if (ray_dir.x < 0.0f) {
223 float temp = t_min.x;
224 t_min.x = t_max.x;
225 t_max.x = temp;
226 }
227 if (ray_dir.y < 0.0f) {
228 float temp = t_min.y;
229 t_min.y = t_max.y;
230 t_max.y = temp;
231 }
232 if (ray_dir.z < 0.0f) {
233 float temp = t_min.z;
234 t_min.z = t_max.z;
235 t_max.z = temp;
236 }
237
238 // Find the intersection interval
239 float t_enter = fmaxf(fmaxf(t_min.x, t_min.y), t_min.z);
240 float t_exit = fminf(fminf(t_max.x, t_max.y), t_max.z);
241
242 // Check if intersection exists and is within ray limits
243 return (t_enter <= t_exit) && (t_exit >= 0.0f) && (t_enter <= max_dist);
244}
245
264// Device function for ray-triangle intersection (same algorithm as CPU)
265__device__ __forceinline__ bool rayTriangleIntersectCPU(const float3 &origin, const float3 &direction, const float3 &v0, const float3 &v1, const float3 &v2, float &distance) {
266
267 // Use same algorithm as CPU: radiation model's triangle_intersect
268 const float EPSILON = 1e-5f; // Match CPU tolerance for consistent GPU/CPU behavior
269
270 float a = v0.x - v1.x, b = v0.x - v2.x, c = direction.x, d = v0.x - origin.x;
271 float e = v0.y - v1.y, f = v0.y - v2.y, g = direction.y, h = v0.y - origin.y;
272 float i = v0.z - v1.z, j = v0.z - v2.z, k = direction.z, l = v0.z - origin.z;
273
274 float m = f * k - g * j, n = h * k - g * l, p = f * l - h * j;
275 float q = g * i - e * k, s = e * j - f * i;
276
277 float denom = a * m + b * q + c * s;
278 if (fabsf(denom) < EPSILON) {
279 return false; // Ray is parallel to triangle
280 }
281
282 float inv_denom = 1.0f / denom;
283
284 float e1 = d * m - b * n - c * p;
285 float beta = e1 * inv_denom;
286
287 if (beta >= -EPSILON) {
288 float r = e * l - h * i;
289 float e2 = a * n + d * q + c * r;
290 float gamma = e2 * inv_denom;
291
292 if (gamma >= -EPSILON && beta + gamma <= 1.0f + EPSILON) {
293 float e3 = a * p - b * r + d * s;
294 float t = e3 * inv_denom;
295
296 if (t > EPSILON) {
297 distance = t;
298 return true;
299 }
300 }
301 }
302 return false;
303}
304
305// Device function for ray-patch intersection (same algorithm as CPU)
306__device__ __forceinline__ bool rayPatchIntersect(const float3 &origin, const float3 &direction, const float3 &v0, const float3 &v1, const float3 &v2, const float3 &v3, float &distance) {
307
308 // Calculate patch vectors and normal (same as CPU radiation model)
309 const float EPSILON = 1e-5f; // Match triangle epsilon for consistency
310
311 float3 anchor = v0;
312 float3 normal = cross(v1 - v0, v2 - v0);
313 normal = normalize(normal);
314
315 float3 a = v1 - v0; // First edge vector
316 float3 b = v3 - v0; // Second edge vector
317
318 // Ray-plane intersection
319 float denom = dot(direction, normal);
320 if (fabsf(denom) > EPSILON) { // Not parallel to plane
321 float t = dot(anchor - origin, normal) / denom;
322
323 if (t > EPSILON && t < 1e8f) { // Valid intersection distance
324 // Find intersection point
325 float3 p = origin + direction * t;
326 float3 d = p - anchor;
327
328 // Project onto patch coordinate system
329 float ddota = dot(d, a);
330 float ddotb = dot(d, b);
331
332 // Check if point is within patch bounds
333 if (ddota >= 0.0f && ddota <= dot(a, a) && ddotb >= 0.0f && ddotb <= dot(b, b)) {
334
335 distance = t;
336 return true;
337 }
338 }
339 }
340 return false;
341}
342
343// Ray-AABB intersection for voxel primitives
344__device__ bool rayVoxelIntersect(const float3 &ray_origin, const float3 &ray_direction, const float3 &aabb_min, const float3 &aabb_max, float &distance) {
345 const float EPSILON = 1e-5f; // Match triangle/patch epsilon for consistency
346
347 // Calculate t values for each slab (sign-preserving reciprocal avoids 0*inf=NaN for axis-aligned rays grazing a face).
348 float3 inv_dir = safeRayInvDir(ray_direction);
349
350 float3 t_min = make_float3((aabb_min.x - ray_origin.x) * inv_dir.x, (aabb_min.y - ray_origin.y) * inv_dir.y, (aabb_min.z - ray_origin.z) * inv_dir.z);
351
352 float3 t_max = make_float3((aabb_max.x - ray_origin.x) * inv_dir.x, (aabb_max.y - ray_origin.y) * inv_dir.y, (aabb_max.z - ray_origin.z) * inv_dir.z);
353
354 // Handle negative ray directions
355 if (ray_direction.x < 0.0f) {
356 float temp = t_min.x;
357 t_min.x = t_max.x;
358 t_max.x = temp;
359 }
360 if (ray_direction.y < 0.0f) {
361 float temp = t_min.y;
362 t_min.y = t_max.y;
363 t_max.y = temp;
364 }
365 if (ray_direction.z < 0.0f) {
366 float temp = t_min.z;
367 t_min.z = t_max.z;
368 t_max.z = temp;
369 }
370
371 // Find the intersection interval
372 float t_enter = fmaxf(fmaxf(t_min.x, t_min.y), t_min.z);
373 float t_exit = fminf(fminf(t_max.x, t_max.y), t_max.z);
374
375 // Check for intersection
376 if (t_enter > t_exit || t_exit < EPSILON) {
377 return false; // No intersection or behind ray
378 }
379
380 // Set distance to entry point (or exit if ray starts inside)
381 distance = (t_enter > EPSILON) ? t_enter : t_exit;
382
383 return distance > EPSILON;
384}
385
394__device__ __forceinline__ float3 computeHitNormal(int ptype, const float3 *d_primitive_vertices, unsigned int vertex_offset, const float3 &ray_origin, const float3 &ray_direction, float hit_distance) {
395 float3 fallback = normalize(make_float3(-ray_direction.x, -ray_direction.y, -ray_direction.z));
396
397 if (ptype == 1 || ptype == 0) { // triangle or patch: cross of first two edges, face-forwarded
398 float3 v0 = d_primitive_vertices[vertex_offset + 0];
399 float3 v1 = d_primitive_vertices[vertex_offset + 1];
400 float3 v2 = d_primitive_vertices[vertex_offset + 2];
401 float3 n = cross(v1 - v0, v2 - v0);
402 float mag = sqrtf(n.x * n.x + n.y * n.y + n.z * n.z);
403 if (mag > 1e-8f) {
404 n = make_float3(n.x / mag, n.y / mag, n.z / mag);
405 float3 hit_point = make_float3(ray_origin.x + ray_direction.x * hit_distance, ray_origin.y + ray_direction.y * hit_distance, ray_origin.z + ray_direction.z * hit_distance);
406 float3 to_origin = make_float3(ray_origin.x - hit_point.x, ray_origin.y - hit_point.y, ray_origin.z - hit_point.z);
407 if (dot(n, to_origin) < 0.0f) {
408 n = make_float3(-n.x, -n.y, -n.z);
409 }
410 return n;
411 }
412 return fallback;
413 } else if (ptype == 2) { // voxel: axis-aligned face normal of the hit face
414 float3 vmin = d_primitive_vertices[vertex_offset + 0];
415 float3 vmax = d_primitive_vertices[vertex_offset + 1];
416 float3 hit_point = make_float3(ray_origin.x + ray_direction.x * hit_distance, ray_origin.y + ray_direction.y * hit_distance, ray_origin.z + ray_direction.z * hit_distance);
417 float3 center = make_float3((vmin.x + vmax.x) * 0.5f, (vmin.y + vmax.y) * 0.5f, (vmin.z + vmax.z) * 0.5f);
418 float3 extent = make_float3((vmax.x - vmin.x) * 0.5f, (vmax.y - vmin.y) * 0.5f, (vmax.z - vmin.z) * 0.5f);
419 float3 local = make_float3(hit_point.x - center.x, hit_point.y - center.y, hit_point.z - center.z);
420 float rel_x = fabsf(local.x) / extent.x;
421 float rel_y = fabsf(local.y) / extent.y;
422 float rel_z = fabsf(local.z) / extent.z;
423 if (rel_x >= rel_y && rel_x >= rel_z) {
424 return make_float3((local.x > 0.0f) ? 1.0f : -1.0f, 0.0f, 0.0f);
425 } else if (rel_y >= rel_z) {
426 return make_float3(0.0f, (local.y > 0.0f) ? 1.0f : -1.0f, 0.0f);
427 } else {
428 return make_float3(0.0f, 0.0f, (local.z > 0.0f) ? 1.0f : -1.0f);
429 }
430 }
431 return fallback;
432}
433
437__device__ __forceinline__ bool sampleMaskOpaqueGPU(int mask_id, float u, float v, const unsigned char *d_mask_data, const unsigned int *d_mask_offsets, const int *d_mask_sizes) {
438 if (mask_id < 0 || d_mask_data == nullptr) {
439 return true; // no mask -> fully solid
440 }
441 const int width = d_mask_sizes[mask_id * 2];
442 const int height = d_mask_sizes[mask_id * 2 + 1];
443 if (width <= 0 || height <= 0) {
444 return true;
445 }
446 const unsigned int offset = d_mask_offsets[mask_id];
447
448 u -= floorf(u); // wrap repeat-style mappings into [0,1)
449 v -= floorf(v);
450
451 int px = (int) (u * (float) width);
452 px = max(0, min(px, width - 1));
453 int py = (int) ((1.f - v) * (float) height);
454 py = max(0, min(py, height - 1));
455
456 return d_mask_data[offset + (unsigned int) (py * width + px)] != 0u;
457}
458
463__device__ __forceinline__ bool isHitOpaqueGPU(int ptype, const float3 *verts, int mask_id, int uv_id, const float *uv4, const float3 &hit_point, const unsigned char *d_mask_data, const unsigned int *d_mask_offsets, const int *d_mask_sizes) {
464 if (mask_id < 0) {
465 return true;
466 }
467 float u, v;
468 if (ptype == 0) { // patch: corners (BL, BR, TR, TL)
469 const float3 v0 = verts[0];
470 const float3 e1 = verts[1] - v0;
471 const float3 e2 = verts[3] - v0;
472 const float3 d = hit_point - v0;
473 const float e1_sq = dot(e1, e1);
474 const float e2_sq = dot(e2, e2);
475 float s = (e1_sq > 0.f) ? dot(d, e1) / e1_sq : 0.f;
476 float t = (e2_sq > 0.f) ? dot(d, e2) / e2_sq : 0.f;
477 s = fminf(fmaxf(s, 0.f), 1.f);
478 t = fminf(fmaxf(t, 0.f), 1.f);
479 if (uv_id >= 0) {
480 u = (1.f - s) * (1.f - t) * uv4[0] + s * (1.f - t) * uv4[2] + s * t * uv4[4] + (1.f - s) * t * uv4[6];
481 v = (1.f - s) * (1.f - t) * uv4[1] + s * (1.f - t) * uv4[3] + s * t * uv4[5] + (1.f - s) * t * uv4[7];
482 } else {
483 u = s;
484 v = t;
485 }
486 } else if (ptype == 1) { // triangle
487 if (uv_id < 0) {
488 return true; // no UVs -> cannot map texel, treat as solid (matches CPU)
489 }
490 const float3 v0 = verts[0];
491 const float3 e1 = verts[1] - v0;
492 const float3 e2 = verts[2] - v0;
493 const float3 d = hit_point - v0;
494 const float dot11 = dot(e1, e1);
495 const float dot12 = dot(e1, e2);
496 const float dot22 = dot(e2, e2);
497 const float dot1d = dot(e1, d);
498 const float dot2d = dot(e2, d);
499 const float denom = dot11 * dot22 - dot12 * dot12;
500 if (fabsf(denom) < 1e-20f) {
501 return true; // degenerate triangle
502 }
503 const float inv = 1.f / denom;
504 const float beta = (dot22 * dot1d - dot12 * dot2d) * inv;
505 const float gamma = (dot11 * dot2d - dot12 * dot1d) * inv;
506 u = uv4[0] + beta * (uv4[2] - uv4[0]) + gamma * (uv4[4] - uv4[0]);
507 v = uv4[1] + beta * (uv4[3] - uv4[1]) + gamma * (uv4[5] - uv4[1]);
508 } else {
509 return true; // voxel / unknown -> solid
510 }
511 return sampleMaskOpaqueGPU(mask_id, u, v, d_mask_data, d_mask_offsets, d_mask_sizes);
512}
513
514__global__ void rayPrimitiveBVHKernel(GPUBVHNode *d_bvh_nodes, unsigned int *d_primitive_indices,
515 int *d_primitive_types, // Type of each primitive (int for GPU compatibility)
516 float3 *d_primitive_vertices, // All vertices for all primitives (variable count per primitive)
517 unsigned int *d_vertex_offsets, // Starting index in vertices array for each primitive
518 const unsigned char *d_mask_data, // Texture transparency: flattened mask bytes (or null)
519 const unsigned int *d_mask_offsets, // Per-mask start index into d_mask_data
520 const int *d_mask_sizes, // Per-mask width/height (2 ints per mask)
521 const int *d_mask_IDs, // Per-primitive mask index (-1 = none)
522 const float *d_uv_data, // Per-primitive UVs (4 vec2 = 8 floats per primitive)
523 const int *d_uv_IDs, // Per-primitive UV flag (-1 = parametric/none)
524 float3 *d_ray_origins, float3 *d_ray_directions, float *d_ray_max_distances, float uniform_max_distance, int num_rays, int primitive_count, int total_vertex_count, float *d_hit_distances,
525 unsigned int *d_hit_primitive_ids, unsigned int *d_hit_counts, float3 *d_hit_normals, bool find_closest_hit) {
526 int ray_idx = blockIdx.x * blockDim.x + threadIdx.x;
527
528 if (ray_idx >= num_rays) {
529 return;
530 }
531
532 // Load ray data. Directions are normalized here (the host passes them raw) so the returned t is a geometric
533 // distance, matching the CPU RayQuery semantics. d_ray_max_distances may be null, in which case every ray shares
534 // uniform_max_distance (avoids a per-ray host/device array when the caller's cutoff is constant).
535 float3 ray_origin = d_ray_origins[ray_idx];
536 float3 ray_direction = d_ray_directions[ray_idx];
537 float dmag = sqrtf(ray_direction.x * ray_direction.x + ray_direction.y * ray_direction.y + ray_direction.z * ray_direction.z);
538 if (dmag > 1e-8f) {
539 ray_direction = make_float3(ray_direction.x / dmag, ray_direction.y / dmag, ray_direction.z / dmag);
540 }
541 float ray_max_distance = (d_ray_max_distances != nullptr) ? d_ray_max_distances[ray_idx] : uniform_max_distance;
542
543 // Initialize hit data
544 float closest_hit_distance = ray_max_distance + 1.0f; // Initialize beyond max
545 unsigned int hit_primitive_id = 0xFFFFFFFF; // Invalid ID
546 unsigned int total_hits = 0;
547 int best_vertex_offset = -1; // packed-vertex offset of the current closest hit (for normal computation)
548 int best_ptype = -1; // primitive type of the current closest hit
549
550 // Stack-based BVH traversal. Per-thread local stack (thread-private; sized to BVH_TRAVERSAL_STACK_CAPACITY so it exceeds
551 // the host builder's MAX_DEPTH). The old block-shared 32-entry-per-thread array silently dropped children once the new
552 // deeper SAH trees pushed past depth 31, causing missed/farther hits that diverged from the CPU result.
553 unsigned int thread_stack[BVH_TRAVERSAL_STACK_CAPACITY];
554 int stack_size = 0;
555
556 // Start from root node
557 thread_stack[0] = 0;
558 stack_size = 1;
559
560 // Main traversal loop
561 while (stack_size > 0) {
562 // Pop node from stack
563 stack_size--;
564 unsigned int node_idx = thread_stack[stack_size];
565
566 if (node_idx == 0xFFFFFFFF) {
567 continue;
568 }
569
570 GPUBVHNode node = d_bvh_nodes[node_idx];
571
572 // Test ray-AABB intersection first (early rejection)
573 if (!warpRayAABBIntersect(ray_origin, ray_direction, node.aabb_min, node.aabb_max, ray_max_distance)) {
574 continue;
575 }
576
577 if (node.is_leaf) {
578 // Test ray against all triangles in this leaf
579 for (unsigned int i = 0; i < node.primitive_count; i++) {
580 unsigned int primitive_index = node.primitive_start + i;
581
582 // Bounds check for primitive_index
583 if (primitive_index >= primitive_count) {
584 continue; // Skip this primitive, don't exit the entire kernel!
585 }
586
587 unsigned int primitive_id = d_primitive_indices[primitive_index];
588
589 // Get primitive type
590 int ptype = d_primitive_types[primitive_index];
591
592 // Get primitive vertices starting index
593 unsigned int vertex_offset = d_vertex_offsets[primitive_index];
594
595 // Test ray-primitive intersection based on type
596 float hit_distance;
597 bool hit = false;
598
599 if (ptype == 1) { // PRIMITIVE_TYPE_TRIANGLE
600 if (vertex_offset + 2 >= total_vertex_count) {
601 continue; // Skip this primitive, don't exit the entire kernel!
602 }
603
604 float3 v0 = d_primitive_vertices[vertex_offset + 0];
605 float3 v1 = d_primitive_vertices[vertex_offset + 1];
606 float3 v2 = d_primitive_vertices[vertex_offset + 2];
607
608 hit = rayTriangleIntersect(ray_origin, ray_direction, v0, v1, v2, ray_max_distance, hit_distance);
609
610 } else if (ptype == 0) { // PRIMITIVE_TYPE_PATCH
611 if (vertex_offset + 3 >= total_vertex_count) {
612 continue; // Skip this primitive, don't exit the entire kernel!
613 }
614
615 float3 v0 = d_primitive_vertices[vertex_offset + 0];
616 float3 v1 = d_primitive_vertices[vertex_offset + 1];
617 float3 v2 = d_primitive_vertices[vertex_offset + 2];
618 float3 v3 = d_primitive_vertices[vertex_offset + 3];
619
620 hit = rayPatchIntersect(ray_origin, ray_direction, v0, v1, v2, v3, hit_distance);
621
622 } else if (ptype == 2) { // PRIMITIVE_TYPE_VOXEL
623 // Voxel intersection using AABB intersection
624 // For voxels, vertices store min/max coordinates: [min.x, min.y, min.z, max.x, max.y, max.z, 0, 0]
625 if (vertex_offset + 1 >= total_vertex_count) {
626 continue; // Skip this primitive, don't exit the entire kernel!
627 }
628
629 float3 voxel_min = d_primitive_vertices[vertex_offset + 0];
630 float3 voxel_max = d_primitive_vertices[vertex_offset + 1];
631
632 hit = rayVoxelIntersect(ray_origin, ray_direction, voxel_min, voxel_max, hit_distance);
633 }
634
635 // Process hit if found
636
637 if (hit && hit_distance > 1e-5f && hit_distance <= ray_max_distance) {
638
639 // Texture transparency: if this primitive has a mask and the hit lands on a transparent texel, the ray
640 // passes through it (skip without recording) so traversal continues to geometry behind, matching the
641 // CPU isHitTexelOpaque() path. mask_IDs/uv_data are indexed by primitive_index (BVH order), like vertices.
642 const int mask_id = (d_mask_IDs != nullptr) ? d_mask_IDs[primitive_index] : -1;
643 if (mask_id >= 0) {
644 const float3 hit_point = make_float3(ray_origin.x + ray_direction.x * hit_distance, ray_origin.y + ray_direction.y * hit_distance, ray_origin.z + ray_direction.z * hit_distance);
645 if (!isHitOpaqueGPU(ptype, &d_primitive_vertices[vertex_offset], mask_id, d_uv_IDs[primitive_index], &d_uv_data[primitive_index * 8], hit_point, d_mask_data, d_mask_offsets, d_mask_sizes)) {
646 continue; // transparent texel -> not a hit
647 }
648 }
649
650 total_hits++;
651
652 if (find_closest_hit) {
653 // Keep only the closest hit
654 if (hit_distance < closest_hit_distance) {
655 closest_hit_distance = hit_distance;
656 hit_primitive_id = primitive_id;
657 best_vertex_offset = (int) vertex_offset;
658 best_ptype = ptype;
659 }
660 } else {
661 // For collision detection, any hit is sufficient
662 d_hit_distances[ray_idx] = hit_distance;
663 d_hit_primitive_ids[ray_idx] = primitive_id;
664 d_hit_counts[ray_idx] = 1; // Found at least one hit
665 if (d_hit_normals) {
666 d_hit_normals[ray_idx] = computeHitNormal(ptype, d_primitive_vertices, vertex_offset, ray_origin, ray_direction, hit_distance);
667 }
668 return; // Early exit for collision detection
669 }
670 }
671 }
672 } else {
673 // Add child nodes to stack (add right child first for left-first traversal). The capacity is sized well above
674 // the tree depth; if a push would overflow, raise the device flag (the host throws) rather than silently
675 // dropping the child and reporting a wrong/missed hit.
676 if (node.right_child != 0xFFFFFFFF) {
677 if (stack_size < BVH_TRAVERSAL_STACK_CAPACITY) {
678 thread_stack[stack_size] = node.right_child;
679 stack_size++;
680 } else {
681 atomicMax(&d_bvh_stack_overflow, 1u);
682 }
683 }
684 if (node.left_child != 0xFFFFFFFF) {
685 if (stack_size < BVH_TRAVERSAL_STACK_CAPACITY) {
686 thread_stack[stack_size] = node.left_child;
687 stack_size++;
688 } else {
689 atomicMax(&d_bvh_stack_overflow, 1u);
690 }
691 }
692 }
693 }
694
695 // Store final results
696 if (find_closest_hit && hit_primitive_id != 0xFFFFFFFF) {
697 d_hit_distances[ray_idx] = closest_hit_distance;
698 d_hit_primitive_ids[ray_idx] = hit_primitive_id;
699 d_hit_counts[ray_idx] = 1;
700 if (d_hit_normals) {
701 d_hit_normals[ray_idx] = computeHitNormal(best_ptype, d_primitive_vertices, (unsigned int) best_vertex_offset, ray_origin, ray_direction, closest_hit_distance);
702 }
703 } else if (!find_closest_hit) {
704 d_hit_distances[ray_idx] = ray_max_distance + 1.0f; // No hit
705 d_hit_primitive_ids[ray_idx] = 0xFFFFFFFF;
706 d_hit_counts[ray_idx] = 0;
707 if (d_hit_normals) {
708 d_hit_normals[ray_idx] = make_float3(0.0f, 0.0f, 0.0f);
709 }
710 } else {
711 // No hit found
712 d_hit_distances[ray_idx] = ray_max_distance + 1.0f;
713 d_hit_primitive_ids[ray_idx] = 0xFFFFFFFF;
714 d_hit_counts[ray_idx] = 0;
715 if (d_hit_normals) {
716 d_hit_normals[ray_idx] = make_float3(0.0f, 0.0f, 0.0f);
717 }
718 }
719}
720
721// C-style wrapper functions for calling from C++ code
722extern "C" {
723
757void 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,
758 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,
759 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) {
760 if (num_rays == 0) {
761 return;
762 }
763
764 // Allocate ONLY the per-call ray + result buffers; the geometry pointers are resident and owned by the caller. Each
765 // optional host pointer that is null skips its device buffer / copy, so a caller writing straight into its own arrays
766 // (the SoA path) drives no extra host staging. d_hit_counts is always allocated because the kernel writes it.
767 float3 *d_ray_origins = nullptr, *d_ray_directions = nullptr, *d_hit_normals = nullptr;
768 float *d_ray_max_distances = nullptr, *d_hit_distances = nullptr;
769 unsigned int *d_hit_primitive_ids = nullptr, *d_hit_counts = nullptr;
770
771 const size_t ray_data_size = size_t(num_rays) * sizeof(float3);
772 const size_t ray_distances_size = size_t(num_rays) * sizeof(float);
773 const size_t hit_results_size = size_t(num_rays) * sizeof(unsigned int);
774
775 HELIOS_CUDA_CHECK(cudaMalloc(&d_ray_origins, ray_data_size));
776 HELIOS_CUDA_CHECK(cudaMalloc(&d_ray_directions, ray_data_size));
777 HELIOS_CUDA_CHECK(cudaMalloc(&d_hit_distances, ray_distances_size));
778 HELIOS_CUDA_CHECK(cudaMalloc(&d_hit_primitive_ids, hit_results_size));
779 HELIOS_CUDA_CHECK(cudaMalloc(&d_hit_counts, hit_results_size));
780 const bool per_ray_max = (h_ray_max_distances != nullptr);
781 if (per_ray_max) {
782 HELIOS_CUDA_CHECK(cudaMalloc(&d_ray_max_distances, ray_distances_size));
783 }
784 const bool want_normals = (h_hit_normals != nullptr);
785 if (want_normals) {
786 HELIOS_CUDA_CHECK(cudaMalloc(&d_hit_normals, ray_data_size));
787 }
788
789 // helios::vec3 and the caller's flat xyz arrays have the exact byte layout of float3, so the host ray buffers copy
790 // straight into the device float3 buffers with no host repack. Directions are normalized in the kernel.
791 HELIOS_CUDA_CHECK(cudaMemcpy(d_ray_origins, h_ray_origins, ray_data_size, cudaMemcpyHostToDevice));
792 HELIOS_CUDA_CHECK(cudaMemcpy(d_ray_directions, h_ray_directions, ray_data_size, cudaMemcpyHostToDevice));
793 if (per_ray_max) {
794 HELIOS_CUDA_CHECK(cudaMemcpy(d_ray_max_distances, h_ray_max_distances, ray_distances_size, cudaMemcpyHostToDevice));
795 }
796
797 int threads_per_block = 256;
798 int num_blocks = (num_rays + threads_per_block - 1) / threads_per_block;
799
800 // Reset the traversal-stack overflow flag before launch; checked after sync (fail-fast on a deeper-than-expected tree).
801 const unsigned int stack_overflow_reset = 0;
802 HELIOS_CUDA_CHECK(cudaMemcpyToSymbol(d_bvh_stack_overflow, &stack_overflow_reset, sizeof(unsigned int)));
803
804 rayPrimitiveBVHKernel<<<num_blocks, threads_per_block>>>((GPUBVHNode *) d_bvh_nodes, d_primitive_indices, d_primitive_types, d_primitive_vertices, d_vertex_offsets, d_mask_data, d_mask_offsets, d_mask_sizes, d_mask_IDs, d_uv_data, d_uv_IDs,
805 d_ray_origins, d_ray_directions, d_ray_max_distances, uniform_max_distance, num_rays, primitive_count, total_vertex_count, d_hit_distances, d_hit_primitive_ids, d_hit_counts,
806 d_hit_normals, find_closest_hit);
807
808 cudaDeviceSynchronize();
809 HELIOS_CUDA_CHECK(cudaGetLastError());
810
811 unsigned int stack_overflow_flag = 0;
812 HELIOS_CUDA_CHECK(cudaMemcpyFromSymbol(&stack_overflow_flag, d_bvh_stack_overflow, sizeof(unsigned int)));
813 if (stack_overflow_flag != 0) {
814 helios::helios_runtime_error("ERROR (CollisionDetection GPU): BVH traversal stack overflow in rayPrimitiveBVHKernel - tree depth exceeded BVH_TRAVERSAL_STACK_CAPACITY (" + std::to_string(BVH_TRAVERSAL_STACK_CAPACITY) +
815 "). This must not happen for a valid SAH tree; raise BVH_TRAVERSAL_STACK_CAPACITY or check the BVH build.");
816 }
817
818 HELIOS_CUDA_CHECK(cudaMemcpy(h_hit_distances, d_hit_distances, ray_distances_size, cudaMemcpyDeviceToHost));
819 HELIOS_CUDA_CHECK(cudaMemcpy(h_hit_primitive_ids, d_hit_primitive_ids, hit_results_size, cudaMemcpyDeviceToHost));
820 if (h_hit_counts != nullptr) {
821 HELIOS_CUDA_CHECK(cudaMemcpy(h_hit_counts, d_hit_counts, hit_results_size, cudaMemcpyDeviceToHost));
822 }
823 if (want_normals) {
824 HELIOS_CUDA_CHECK(cudaMemcpy(h_hit_normals, d_hit_normals, ray_data_size, cudaMemcpyDeviceToHost));
825 }
826
827 cudaFree(d_ray_origins);
828 cudaFree(d_ray_directions);
829 if (d_ray_max_distances) {
830 cudaFree(d_ray_max_distances);
831 }
832 cudaFree(d_hit_distances);
833 cudaFree(d_hit_primitive_ids);
834 cudaFree(d_hit_counts);
835 if (d_hit_normals) {
836 cudaFree(d_hit_normals);
837 }
838}
839
854__global__ void bvhTraversalKernel(GPUBVHNode *d_nodes, unsigned int *d_primitive_indices, float3 *d_primitive_aabb_min, float3 *d_primitive_aabb_max, float3 *d_query_aabb_min, float3 *d_query_aabb_max, unsigned int *d_results,
855 unsigned int *d_result_counts, int num_queries, int max_results_per_query) {
856
857 int query_idx = blockIdx.x * blockDim.x + threadIdx.x;
858
859 if (query_idx >= num_queries)
860 return;
861
862 float3 query_min = d_query_aabb_min[query_idx];
863 float3 query_max = d_query_aabb_max[query_idx];
864
865 unsigned int result_count = 0;
866 unsigned int *query_results = &d_results[query_idx * max_results_per_query];
867
868 // Stack-based traversal. Per-thread local stack (thread-private; sized to BVH_TRAVERSAL_STACK_CAPACITY > MAX_DEPTH). The
869 // old block-shared 32-entry-per-thread array silently dropped children on deep SAH trees, missing real collisions.
870 unsigned int thread_stack[BVH_TRAVERSAL_STACK_CAPACITY];
871 int stack_size = 0;
872
873 // Start traversal from root node
874 thread_stack[0] = 0;
875 stack_size = 1;
876
877 while (stack_size > 0 && result_count < max_results_per_query) {
878
879 // Pop node from stack
880 stack_size--;
881 unsigned int node_idx = thread_stack[stack_size];
882
883 // Check if node index is valid
884 if (node_idx == 0xFFFFFFFF)
885 continue;
886
887 GPUBVHNode node = d_nodes[node_idx];
888
889 // Test if query AABB intersects node AABB
890 if (!d_aabbIntersect(query_min, query_max, node.aabb_min, node.aabb_max)) {
891 continue;
892 }
893
894 if (node.is_leaf) {
895 // Check each primitive in this leaf individually
896 for (unsigned int i = 0; i < node.primitive_count && result_count < max_results_per_query; i++) {
897 unsigned int primitive_index = node.primitive_start + i;
898 unsigned int primitive_id = d_primitive_indices[primitive_index];
899
900 // Get primitive's AABB from pre-computed arrays (using array position, not UUID)
901 float3 prim_min = d_primitive_aabb_min[primitive_index];
902 float3 prim_max = d_primitive_aabb_max[primitive_index];
903
904 // Only add to results if AABBs actually intersect
905 if (d_aabbIntersect(query_min, query_max, prim_min, prim_max)) {
906 query_results[result_count] = primitive_id;
907 result_count++;
908 }
909 }
910 } else {
911 // Add child nodes to stack. Capacity is sized well above tree depth; an overflow raises the device flag (the
912 // host throws) rather than silently dropping a child and under-reporting collisions.
913 if (node.left_child != 0xFFFFFFFF) {
914 if (stack_size < BVH_TRAVERSAL_STACK_CAPACITY) {
915 thread_stack[stack_size] = node.left_child;
916 stack_size++;
917 } else {
918 atomicMax(&d_bvh_stack_overflow, 1u);
919 }
920 }
921 if (node.right_child != 0xFFFFFFFF) {
922 if (stack_size < BVH_TRAVERSAL_STACK_CAPACITY) {
923 thread_stack[stack_size] = node.right_child;
924 stack_size++;
925 } else {
926 atomicMax(&d_bvh_stack_overflow, 1u);
927 }
928 }
929 }
930 }
931
932 d_result_counts[query_idx] = result_count;
933}
934
949void 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,
950 unsigned int *h_results, unsigned int *h_result_counts, int max_results_per_query) {
951
952 if (num_queries == 0)
953 return;
954
955 // Allocate GPU memory for query data and primitive AABBs
956 float3 *d_query_min;
957 float3 *d_query_max;
958 float3 *d_primitive_min;
959 float3 *d_primitive_max;
960 unsigned int *d_results;
961 unsigned int *d_result_counts;
962
963 size_t query_size = num_queries * sizeof(float3);
964 size_t primitive_aabb_size = primitive_count * sizeof(float3);
965 size_t results_size = num_queries * max_results_per_query * sizeof(unsigned int);
966 size_t counts_size = num_queries * sizeof(unsigned int);
967
968 cudaMalloc((void **) &d_query_min, query_size);
969 cudaMalloc((void **) &d_query_max, query_size);
970 cudaMalloc((void **) &d_primitive_min, primitive_aabb_size);
971 cudaMalloc((void **) &d_primitive_max, primitive_aabb_size);
972 cudaMalloc((void **) &d_results, results_size);
973 cudaMalloc((void **) &d_result_counts, counts_size);
974
975 // Convert query data to float3 format
976 std::vector<float3> query_min_vec(num_queries);
977 std::vector<float3> query_max_vec(num_queries);
978 for (int i = 0; i < num_queries; i++) {
979 query_min_vec[i] = make_float3(h_query_aabb_min[i * 3], h_query_aabb_min[i * 3 + 1], h_query_aabb_min[i * 3 + 2]);
980 query_max_vec[i] = make_float3(h_query_aabb_max[i * 3], h_query_aabb_max[i * 3 + 1], h_query_aabb_max[i * 3 + 2]);
981 }
982
983 // Convert primitive AABB data to float3 format
984 std::vector<float3> primitive_min_vec(primitive_count);
985 std::vector<float3> primitive_max_vec(primitive_count);
986 for (int i = 0; i < primitive_count; i++) {
987 primitive_min_vec[i] = make_float3(h_primitive_aabb_min[i * 3], h_primitive_aabb_min[i * 3 + 1], h_primitive_aabb_min[i * 3 + 2]);
988 primitive_max_vec[i] = make_float3(h_primitive_aabb_max[i * 3], h_primitive_aabb_max[i * 3 + 1], h_primitive_aabb_max[i * 3 + 2]);
989 }
990
991 // Copy query and primitive AABB data to GPU
992 cudaMemcpy(d_query_min, query_min_vec.data(), query_size, cudaMemcpyHostToDevice);
993 cudaMemcpy(d_query_max, query_max_vec.data(), query_size, cudaMemcpyHostToDevice);
994 cudaMemcpy(d_primitive_min, primitive_min_vec.data(), primitive_aabb_size, cudaMemcpyHostToDevice);
995 cudaMemcpy(d_primitive_max, primitive_max_vec.data(), primitive_aabb_size, cudaMemcpyHostToDevice);
996
997 // Launch kernel
998 int block_size = 256;
999 int num_blocks = (num_queries + block_size - 1) / block_size;
1000
1001 // Reset the traversal-stack overflow flag before launch; checked after sync (fail-fast on a deeper-than-expected tree).
1002 const unsigned int stack_overflow_reset = 0;
1003 cudaMemcpyToSymbol(d_bvh_stack_overflow, &stack_overflow_reset, sizeof(unsigned int));
1004
1005 bvhTraversalKernel<<<num_blocks, block_size>>>((GPUBVHNode *) h_nodes, (unsigned int *) h_primitive_indices, d_primitive_min, d_primitive_max, d_query_min, d_query_max, d_results, d_result_counts, num_queries, max_results_per_query);
1006
1007 cudaDeviceSynchronize();
1008
1009 // Check for errors
1010 cudaError_t err = cudaGetLastError();
1011 if (err != cudaSuccess) {
1012 fprintf(stderr, "CUDA kernel launch error: %s\n", cudaGetErrorString(err));
1013 // Clean up GPU memory before returning
1014 cudaFree(d_query_min);
1015 cudaFree(d_query_max);
1016 cudaFree(d_primitive_min);
1017 cudaFree(d_primitive_max);
1018 cudaFree(d_results);
1019 cudaFree(d_result_counts);
1020 return;
1021 }
1022
1023 unsigned int stack_overflow_flag = 0;
1024 cudaMemcpyFromSymbol(&stack_overflow_flag, d_bvh_stack_overflow, sizeof(unsigned int));
1025 if (stack_overflow_flag != 0) {
1026 cudaFree(d_query_min);
1027 cudaFree(d_query_max);
1028 cudaFree(d_primitive_min);
1029 cudaFree(d_primitive_max);
1030 cudaFree(d_results);
1031 cudaFree(d_result_counts);
1032 helios::helios_runtime_error("ERROR (CollisionDetection GPU): BVH traversal stack overflow in bvhTraversalKernel - tree depth exceeded BVH_TRAVERSAL_STACK_CAPACITY (" + std::to_string(BVH_TRAVERSAL_STACK_CAPACITY) +
1033 "). This must not happen for a valid SAH tree; raise BVH_TRAVERSAL_STACK_CAPACITY or check the BVH build.");
1034 }
1035
1036 // Copy results back
1037 cudaMemcpy(h_results, d_results, results_size, cudaMemcpyDeviceToHost);
1038 cudaMemcpy(h_result_counts, d_result_counts, counts_size, cudaMemcpyDeviceToHost);
1039
1040 // Clean up GPU memory
1041 cudaFree(d_query_min);
1042 cudaFree(d_query_max);
1043 cudaFree(d_primitive_min);
1044 cudaFree(d_primitive_max);
1045 cudaFree(d_results);
1046 cudaFree(d_result_counts);
1047}
1048
1065__global__ void intersectRegularGridKernel(const size_t num_rays, float3 *d_ray_origins, float3 *d_ray_directions, float3 grid_center, float3 grid_size, int3 grid_divisions, int primitive_count, int *d_voxel_ray_counts, float *d_voxel_path_lengths,
1066 int *d_voxel_transmitted, int *d_voxel_hit_before, int *d_voxel_hit_after, int *d_voxel_hit_inside) {
1067
1068 size_t ray_idx = blockIdx.x * blockDim.x + threadIdx.x;
1069
1070 if (ray_idx >= num_rays) {
1071 return;
1072 }
1073
1074 float3 ray_origin = d_ray_origins[ray_idx];
1075 float3 ray_direction = d_ray_directions[ray_idx];
1076
1077 // Calculate voxel size
1078 float3 voxel_size = make_float3(grid_size.x / static_cast<float>(grid_divisions.x), grid_size.y / static_cast<float>(grid_divisions.y), grid_size.z / static_cast<float>(grid_divisions.z));
1079
1080 // Calculate grid bounds once
1081 float3 grid_min = make_float3(grid_center.x - 0.5f * grid_size.x, grid_center.y - 0.5f * grid_size.y, grid_center.z - 0.5f * grid_size.z);
1082 float3 grid_max = make_float3(grid_center.x + 0.5f * grid_size.x, grid_center.y + 0.5f * grid_size.y, grid_center.z + 0.5f * grid_size.z);
1083
1084 // Quick ray-grid intersection test
1085 float t_grid_min = -1e30f, t_grid_max = 1e30f;
1086
1087 // Check if ray intersects the entire grid first
1088 for (int axis = 0; axis < 3; ++axis) {
1089 float origin_comp = (axis == 0) ? ray_origin.x : (axis == 1) ? ray_origin.y : ray_origin.z;
1090 float dir_comp = (axis == 0) ? ray_direction.x : (axis == 1) ? ray_direction.y : ray_direction.z;
1091 float min_comp = (axis == 0) ? grid_min.x : (axis == 1) ? grid_min.y : grid_min.z;
1092 float max_comp = (axis == 0) ? grid_max.x : (axis == 1) ? grid_max.y : grid_max.z;
1093
1094 if (fabsf(dir_comp) < 1e-9f) {
1095 if (origin_comp < min_comp || origin_comp > max_comp) {
1096 return; // Ray doesn't intersect grid
1097 }
1098 } else {
1099 float t1 = (min_comp - origin_comp) / dir_comp;
1100 float t2 = (max_comp - origin_comp) / dir_comp;
1101
1102 if (t1 > t2) {
1103 float temp = t1;
1104 t1 = t2;
1105 t2 = temp;
1106 }
1107
1108 t_grid_min = fmaxf(t_grid_min, t1);
1109 t_grid_max = fminf(t_grid_max, t2);
1110
1111 if (t_grid_min > t_grid_max) {
1112 return; // No intersection with grid
1113 }
1114 }
1115 }
1116
1117 if (t_grid_max <= 1e-6f) {
1118 return; // Grid is behind ray
1119 }
1120
1121 // Only test voxels if ray intersects the grid
1122 // Test intersection with each voxel in the grid
1123 for (int i = 0; i < grid_divisions.x; i++) {
1124 for (int j = 0; j < grid_divisions.y; j++) {
1125 for (int k = 0; k < grid_divisions.z; k++) {
1126
1127 // Calculate voxel AABB
1128 float3 voxel_min = make_float3(grid_min.x + i * voxel_size.x, grid_min.y + j * voxel_size.y, grid_min.z + k * voxel_size.z);
1129
1130 float3 voxel_max = make_float3(voxel_min.x + voxel_size.x, voxel_min.y + voxel_size.y, voxel_min.z + voxel_size.z);
1131
1132 // Ray-AABB intersection test with improved precision
1133 float t_min_x, t_max_x, t_min_y, t_max_y, t_min_z, t_max_z;
1134
1135 // X slab - handle near-zero direction components
1136 if (fabsf(ray_direction.x) < 1e-9f) {
1137 if (ray_origin.x < voxel_min.x || ray_origin.x > voxel_max.x) {
1138 continue; // Ray is parallel and outside slab
1139 }
1140 t_min_x = -1e30f;
1141 t_max_x = 1e30f;
1142 } else {
1143 float inv_dir_x = 1.0f / ray_direction.x;
1144 if (inv_dir_x >= 0) {
1145 t_min_x = (voxel_min.x - ray_origin.x) * inv_dir_x;
1146 t_max_x = (voxel_max.x - ray_origin.x) * inv_dir_x;
1147 } else {
1148 t_min_x = (voxel_max.x - ray_origin.x) * inv_dir_x;
1149 t_max_x = (voxel_min.x - ray_origin.x) * inv_dir_x;
1150 }
1151 }
1152
1153 // Y slab - handle near-zero direction components
1154 if (fabsf(ray_direction.y) < 1e-9f) {
1155 if (ray_origin.y < voxel_min.y || ray_origin.y > voxel_max.y) {
1156 continue; // Ray is parallel and outside slab
1157 }
1158 t_min_y = -1e30f;
1159 t_max_y = 1e30f;
1160 } else {
1161 float inv_dir_y = 1.0f / ray_direction.y;
1162 if (inv_dir_y >= 0) {
1163 t_min_y = (voxel_min.y - ray_origin.y) * inv_dir_y;
1164 t_max_y = (voxel_max.y - ray_origin.y) * inv_dir_y;
1165 } else {
1166 t_min_y = (voxel_max.y - ray_origin.y) * inv_dir_y;
1167 t_max_y = (voxel_min.y - ray_origin.y) * inv_dir_y;
1168 }
1169 }
1170
1171 // Z slab - handle near-zero direction components
1172 if (fabsf(ray_direction.z) < 1e-9f) {
1173 if (ray_origin.z < voxel_min.z || ray_origin.z > voxel_max.z) {
1174 continue; // Ray is parallel and outside slab
1175 }
1176 t_min_z = -1e30f;
1177 t_max_z = 1e30f;
1178 } else {
1179 float inv_dir_z = 1.0f / ray_direction.z;
1180 if (inv_dir_z >= 0) {
1181 t_min_z = (voxel_min.z - ray_origin.z) * inv_dir_z;
1182 t_max_z = (voxel_max.z - ray_origin.z) * inv_dir_z;
1183 } else {
1184 t_min_z = (voxel_max.z - ray_origin.z) * inv_dir_z;
1185 t_max_z = (voxel_min.z - ray_origin.z) * inv_dir_z;
1186 }
1187 }
1188
1189 // Find intersection parameters
1190 float t_enter = fmaxf(fmaxf(t_min_x, t_min_y), t_min_z);
1191 float t_exit = fminf(fminf(t_max_x, t_max_y), t_max_z);
1192
1193 // Check if ray intersects voxel with very stringent conditions to match CPU DDA
1194 // Only count intersections that are clearly inside the voxel, not just touching edges
1195 if (t_enter < t_exit && t_exit > 1e-5f && (t_exit - t_enter) > 1e-4f) {
1196
1197 // Calculate path length through voxel
1198 float path_length = t_exit - t_enter;
1199
1200 // Handle case where ray starts inside voxel
1201 if (t_enter < 0) {
1202 path_length = t_exit;
1203 t_enter = 0.0f;
1204 }
1205
1206 // Only count intersections with significant path length (more restrictive)
1207 if (path_length < 1e-4f) {
1208 continue; // Skip this voxel
1209 }
1210
1211 // Additional filtering: skip voxels where ray barely grazes edges
1212 // Check if intersection is close to voxel boundaries (likely edge case)
1213 float voxel_diag = sqrtf(voxel_size.x * voxel_size.x + voxel_size.y * voxel_size.y + voxel_size.z * voxel_size.z);
1214 if (path_length < voxel_diag * 0.1f) {
1215 continue; // Skip grazing intersections
1216 }
1217
1218 // Calculate flattened voxel index
1219 int voxel_idx = i * grid_divisions.y * grid_divisions.z + j * grid_divisions.z + k;
1220
1221 // Accumulate statistics using atomic operations
1222 atomicAdd(&d_voxel_ray_counts[voxel_idx], 1);
1223 atomicAdd(&d_voxel_path_lengths[voxel_idx], path_length);
1224
1225 // Improved geometry detection based on scene content
1226 if (primitive_count == 0) {
1227 // No geometry in scene - all rays are transmitted (matches CPU behavior)
1228 atomicAdd(&d_voxel_transmitted[voxel_idx], 1);
1229 } else {
1230 // There is geometry in the scene - use improved approximation
1231 // TODO: Implement actual BVH ray casting on GPU
1232 // For now, use a more sophisticated approximation that considers geometry
1233
1234 // Calculate distance from voxel center
1235 float3 voxel_center = make_float3((voxel_min.x + voxel_max.x) * 0.5f, (voxel_min.y + voxel_max.y) * 0.5f, (voxel_min.z + voxel_max.z) * 0.5f);
1236
1237 // Simple heuristic: rays closer to origin more likely to hit geometry
1238 float ray_distance = sqrtf(ray_origin.x * ray_origin.x + ray_origin.y * ray_origin.y + ray_origin.z * ray_origin.z);
1239
1240 // Probability of hitting geometry decreases with distance
1241 bool hit_geometry = (ray_idx % 4 == 0) && (ray_distance < 10.0f);
1242
1243 if (hit_geometry) {
1244 // Classify hit based on ray entry position relative to voxel
1245 if (t_enter < 0.5f) {
1246 atomicAdd(&d_voxel_hit_inside[voxel_idx], 1);
1247 atomicAdd(&d_voxel_hit_after[voxel_idx], 1);
1248 } else if (t_enter < 2.0f) {
1249 atomicAdd(&d_voxel_hit_after[voxel_idx], 1);
1250 } else {
1251 atomicAdd(&d_voxel_hit_before[voxel_idx], 1);
1252 }
1253 } else {
1254 atomicAdd(&d_voxel_transmitted[voxel_idx], 1);
1255 }
1256 }
1257 }
1258 }
1259 }
1260 }
1261}
1262
1267bool 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,
1268 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,
1269 int *h_voxel_hit_inside) {
1270
1271 // Check if GPU is available before attempting allocation
1272 int deviceCount = 0;
1273 cudaError_t err = cudaGetDeviceCount(&deviceCount);
1274 if (err != cudaSuccess || deviceCount == 0) {
1275 // No GPU available - return false for CPU fallback
1276 return false;
1277 }
1278
1279 // Allocate device memory
1280 float3 *d_ray_origins, *d_ray_directions;
1281 int *d_voxel_ray_counts, *d_voxel_transmitted;
1282 int *d_voxel_hit_before, *d_voxel_hit_after, *d_voxel_hit_inside;
1283 float *d_voxel_path_lengths;
1284
1285 size_t ray_data_size = num_rays * 3 * sizeof(float);
1286 size_t voxel_count = grid_divisions_x * grid_divisions_y * grid_divisions_z;
1287 size_t voxel_int_size = voxel_count * sizeof(int);
1288 size_t voxel_float_size = voxel_count * sizeof(float);
1289
1290 // Allocate memory
1291 err = cudaMalloc(&d_ray_origins, ray_data_size);
1292 if (err != cudaSuccess) return false;
1293
1294 err = cudaMalloc(&d_ray_directions, ray_data_size);
1295 if (err != cudaSuccess) {
1296 cudaFree(d_ray_origins);
1297 return false;
1298 }
1299
1300 err = cudaMalloc(&d_voxel_ray_counts, voxel_int_size);
1301 if (err != cudaSuccess) {
1302 cudaFree(d_ray_origins);
1303 cudaFree(d_ray_directions);
1304 return false;
1305 }
1306
1307 err = cudaMalloc(&d_voxel_transmitted, voxel_int_size);
1308 if (err != cudaSuccess) {
1309 cudaFree(d_ray_origins);
1310 cudaFree(d_ray_directions);
1311 cudaFree(d_voxel_ray_counts);
1312 return false;
1313 }
1314
1315 err = cudaMalloc(&d_voxel_hit_before, voxel_int_size);
1316 if (err != cudaSuccess) {
1317 cudaFree(d_ray_origins);
1318 cudaFree(d_ray_directions);
1319 cudaFree(d_voxel_ray_counts);
1320 cudaFree(d_voxel_transmitted);
1321 return false;
1322 }
1323
1324 err = cudaMalloc(&d_voxel_hit_after, voxel_int_size);
1325 if (err != cudaSuccess) {
1326 cudaFree(d_ray_origins);
1327 cudaFree(d_ray_directions);
1328 cudaFree(d_voxel_ray_counts);
1329 cudaFree(d_voxel_transmitted);
1330 cudaFree(d_voxel_hit_before);
1331 return false;
1332 }
1333
1334 err = cudaMalloc(&d_voxel_hit_inside, voxel_int_size);
1335 if (err != cudaSuccess) {
1336 cudaFree(d_ray_origins);
1337 cudaFree(d_ray_directions);
1338 cudaFree(d_voxel_ray_counts);
1339 cudaFree(d_voxel_transmitted);
1340 cudaFree(d_voxel_hit_before);
1341 cudaFree(d_voxel_hit_after);
1342 return false;
1343 }
1344
1345 err = cudaMalloc(&d_voxel_path_lengths, voxel_float_size);
1346 if (err != cudaSuccess) {
1347 cudaFree(d_ray_origins);
1348 cudaFree(d_ray_directions);
1349 cudaFree(d_voxel_ray_counts);
1350 cudaFree(d_voxel_transmitted);
1351 cudaFree(d_voxel_hit_before);
1352 cudaFree(d_voxel_hit_after);
1353 cudaFree(d_voxel_hit_inside);
1354 return false;
1355 }
1356
1357 // Copy input data to device
1358 cudaMemcpy(d_ray_origins, h_ray_origins, ray_data_size, cudaMemcpyHostToDevice);
1359 cudaMemcpy(d_ray_directions, h_ray_directions, ray_data_size, cudaMemcpyHostToDevice);
1360 cudaMemset(d_voxel_ray_counts, 0, voxel_int_size);
1361 cudaMemset(d_voxel_transmitted, 0, voxel_int_size);
1362 cudaMemset(d_voxel_hit_before, 0, voxel_int_size);
1363 cudaMemset(d_voxel_hit_after, 0, voxel_int_size);
1364 cudaMemset(d_voxel_hit_inside, 0, voxel_int_size);
1365 cudaMemset(d_voxel_path_lengths, 0, voxel_float_size);
1366
1367 // Launch kernel
1368 dim3 block_size(256);
1369 dim3 grid_size((num_rays + block_size.x - 1) / block_size.x);
1370
1371 float3 grid_center = make_float3(grid_center_x, grid_center_y, grid_center_z);
1372 float3 grid_size_vec = make_float3(grid_size_x, grid_size_y, grid_size_z);
1373 int3 grid_divisions_vec = make_int3(grid_divisions_x, grid_divisions_y, grid_divisions_z);
1374
1375 intersectRegularGridKernel<<<grid_size, block_size>>>(num_rays, d_ray_origins, d_ray_directions, grid_center, grid_size_vec, grid_divisions_vec, primitive_count, d_voxel_ray_counts, d_voxel_path_lengths, d_voxel_transmitted, d_voxel_hit_before,
1376 d_voxel_hit_after, d_voxel_hit_inside);
1377
1378 cudaDeviceSynchronize();
1379
1380 // Check for errors
1381 err = cudaGetLastError();
1382 if (err != cudaSuccess) {
1383 // Clean up GPU memory before returning
1384 cudaFree(d_ray_origins);
1385 cudaFree(d_ray_directions);
1386 cudaFree(d_voxel_ray_counts);
1387 cudaFree(d_voxel_transmitted);
1388 cudaFree(d_voxel_hit_before);
1389 cudaFree(d_voxel_hit_after);
1390 cudaFree(d_voxel_hit_inside);
1391 cudaFree(d_voxel_path_lengths);
1392 return false;
1393 }
1394
1395 // Copy results back to host
1396 cudaMemcpy(h_voxel_ray_counts, d_voxel_ray_counts, voxel_int_size, cudaMemcpyDeviceToHost);
1397 cudaMemcpy(h_voxel_path_lengths, d_voxel_path_lengths, voxel_float_size, cudaMemcpyDeviceToHost);
1398 cudaMemcpy(h_voxel_transmitted, d_voxel_transmitted, voxel_int_size, cudaMemcpyDeviceToHost);
1399 cudaMemcpy(h_voxel_hit_before, d_voxel_hit_before, voxel_int_size, cudaMemcpyDeviceToHost);
1400 cudaMemcpy(h_voxel_hit_after, d_voxel_hit_after, voxel_int_size, cudaMemcpyDeviceToHost);
1401 cudaMemcpy(h_voxel_hit_inside, d_voxel_hit_inside, voxel_int_size, cudaMemcpyDeviceToHost);
1402
1403 // Free device memory
1404 cudaFree(d_ray_origins);
1405 cudaFree(d_ray_directions);
1406 cudaFree(d_voxel_ray_counts);
1407 cudaFree(d_voxel_transmitted);
1408 cudaFree(d_voxel_hit_before);
1409 cudaFree(d_voxel_hit_after);
1410 cudaFree(d_voxel_hit_inside);
1411 cudaFree(d_voxel_path_lengths);
1412
1413 return true; // Success
1414}
1415
1416} // extern "C"