| Dependencies | OpenMP (recommended), NVIDIA CUDA 9.0+ (optional) |
| CMakeLists.txt | set( PLUGINS "collisiondetection" ) |
| Header File | include "CollisionDetection.h" |
| Class | CollisionDetection |
Dependencies
| Package | Status |
td |
td |
td |
| OpenMP | Recommended | Usually available (via libomp) | Usually available | Usually available |
| NVIDIA CUDA 9.0+ (optional) | Optional | Mac OSX: Not available | Use CUDA installer | Use CUDA installer |
Performance Note: OpenMP is strongly recommended for production use. The plugin will work without OpenMP using serial CPU execution, but performance will be significantly slower for large scenes.
For help choosing the correct CUDA toolkit version and accompanying OptiX version for your system, consult this page: Choosing the right CUDA and OptiX version
Useful OS-specific information for installing CUDA can be found here: Install and Set-up
If you are using a PC, it is likely you will need to increase the GPU timeout in the registry, otherwise calculations lasting longer than 2 secs. will timeout and kill your program. A guide on how to increase the timeout can be found here: Increasing graphics driver timeout
Note: CUDA is optional for this plugin. The collision detection system will work on CPU-only systems. OpenMP provides parallel CPU execution and is recommended for best performance. When neither GPU nor OpenMP is available, the plugin falls back to serial CPU execution with a performance warning.
Introduction
This plugin provides comprehensive collision detection and spatial analysis capabilities for arbitrary 3D geometry using high-performance GPU-accelerated spatial data structures. The system uses a Bounding Volume Hierarchy (BVH) to efficiently perform various spatial queries including collision detection, ray tracing, cone-based path finding, and grid-based statistical analysis.
The collision detection system supports both CPU and GPU acceleration with automatic fallback. When CUDA is available, the plugin automatically uses GPU kernels to perform parallel spatial queries, providing significant speedup for large scenes. The system gracefully falls back to CPU computation when GPU resources are unavailable.
Key features include:
- Collision Detection: Fast primitive-to-primitive and primitive-to-object collision queries
- Ray Tracing: High-performance ray casting with batch processing and GPU acceleration
- Cone-Based Queries: Optimal path detection and obstacle avoidance for directional applications
- Grid-Based Analysis: Voxel intersection and statistical analysis for spatial modeling
- Advanced BVH Management: Automatic rebuilding, tree-based optimization, and hierarchical structures
- GPU Acceleration: CUDA-accelerated queries with Structure-of-Arrays (SOA) optimization
- Spatial Optimization: Distance-based filtering and nearest neighbor queries
- Memory Efficiency: Optimized data structures with automatic memory management
CollisionDetection Class Constructor
The CollisionDetection class is initialized by passing a pointer to the Helios context as an argument to the constructor. The constructor automatically:
- Validates the context pointer (throws exception if null)
- Enables GPU acceleration if CUDA is available
- Sets default parameters: max_collision_distance = 10.0f, tree_isolation_distance = 5.0f
- Enables automatic BVH rebuilds and diagnostic messages
Core Data Structures
The CollisionDetection plugin defines several core data structures for different types of spatial queries:
Ray Tracing Structures
struct RayQuery {
float max_distance;
std::vector<uint> target_UUIDs;
};
struct HitResult {
bool hit;
float distance;
float path_length;
};
struct RayTracingStats {
size_t total_rays_cast;
size_t total_hits;
size_t bvh_nodes_visited;
float average_ray_distance;
};
Cone-Based Query Structures
struct Cone {
float half_angle;
float height;
};
struct OptimalPathResult {
int collisionCount;
float confidence;
};
Using the Collision Detection Plugin
Basic Setup
The collision detection class provides comprehensive spatial query capabilities. To begin, declare an instance of the CollisionDetection class:
Basic Collision Detection
The collision detection system features automatic BVH management. The BVH is built automatically when needed:
uint target_UUID = some_primitive_UUID;
std::vector<uint> colliding_UUIDs = collision.findCollisions(target_UUID);
std::vector<uint> query_UUIDs = {UUID1, UUID2, UUID3};
std::vector<uint> all_collisions = collision.findCollisions(query_UUIDs);
std::vector<uint> primitive_UUIDs = {UUID1, UUID2};
std::vector<uint> object_IDs = {objID1, objID2};
std::vector<uint> mixed_collisions = collision.findCollisions(primitive_UUIDs, object_IDs);
std::vector<uint> target_UUIDs = {UUID3, UUID4};
std::vector<uint> target_objects = {objID3};
std::vector<uint> restricted_collisions = collision.findCollisions(
primitive_UUIDs, object_IDs, target_UUIDs, target_objects);
Ray Tracing Operations
The plugin provides comprehensive ray tracing capabilities:
HitResult result = collision.castRay(origin, direction, 10.0f);
if( result.hit ){
std::cout << "Hit primitive " << result.primitive_UUID
<< " at distance " << result.distance << std::endl;
std::cout << "Intersection point: " << result.intersection_point << std::endl;
}
std::vector<RayQuery> queries;
queries.push_back({origin, direction, 10.0f, {}});
queries.push_back({
make_vec3(1, -1, 0), direction, 10.0f, {}});
RayTracingStats stats;
std::vector<HitResult> results = collision.castRays(queries, &stats);
std::cout << "Cast " << stats.total_rays_cast << " rays, "
<< stats.total_hits << " hits" << std::endl;
Cone-Based Spatial Queries
Find optimal paths through cones for directional obstacle avoidance:
float half_angle =
M_PI / 6.0f;
float height = 50.0f;
int samples = 64;
OptimalPathResult result = collision.findOptimalConePath(
apex, central_axis, half_angle, height, samples);
std::cout << "Optimal direction: " << result.direction << std::endl;
std::cout << "Collision count: " << result.collisionCount << std::endl;
std::cout << "Confidence: " << result.confidence << std::endl;
float perception_angle =
M_PI / 4.0f;
float segment_length = 1.0f;
auto path_result = collision.findOptimalConePath(shoot_tip, growth_direction,
perception_angle, segment_length, 128);
helios::vec3 new_tip = shoot_tip + path_result.direction * segment_length;
Example Applications
Plant Growth Simulation
Complete plant growth simulation with collision avoidance:
for( int growth_step = 0; growth_step < 100; ++growth_step ){
float perception_angle =
M_PI / 3.0f;
float segment_length = 0.2f;
auto path_result = collision.findOptimalConePath(
shoot_tip, preferred_direction, perception_angle, segment_length, 128);
if( path_result.confidence > 0.7f ){
helios::vec3 new_tip = shoot_tip + path_result.direction * segment_length;
uint new_segment = context.addCylinder(shoot_tip, new_tip, 0.02f);
shoot_tip = new_tip;
preferred_direction = path_result.direction;
std::cout << "Growth step " << growth_step << ": confidence "
<< path_result.confidence << ", collisions "
<< path_result.collisionCount << std::endl;
} else {
std::cout << "Growth blocked - stopping simulation" << std::endl;
break;
}
}
Ray Tracing Operations
Simulate realistic LiDAR scanning with ray tracing:
collision.enableGPUAcceleration();
float scan_range = 50.0f;
int azimuth_samples = 360;
int elevation_samples = 180;
std::vector<RayQuery> lidar_rays;
lidar_rays.reserve(azimuth_samples * elevation_samples);
for( int az = 0; az < azimuth_samples; ++az ){
for( int el = 0; el < elevation_samples; ++el ){
float azimuth = 2.0f *
M_PI * az / azimuth_samples;
float elevation =
M_PI * (el - elevation_samples/2) / elevation_samples;
cos(elevation) * cos(azimuth),
cos(elevation) * sin(azimuth),
sin(elevation)
);
lidar_rays.emplace_back(RayQuery{
scanner_position, direction, scan_range, {}
});
}
}
RayTracingStats scan_stats;
std::vector<HitResult> scan_results = collision.castRays(lidar_rays, &scan_stats);
std::vector<helios::vec3> point_cloud;
for( const auto& result : scan_results ){
if( result.hit ){
point_cloud.push_back(result.intersection_point);
}
}
std::cout << "LiDAR scan complete: " << point_cloud.size() << " points captured" << std::endl;
std::cout << "Hit rate: " << (float)scan_stats.total_hits / scan_stats.total_rays_cast << std::endl;
Advanced Features
Error Handling
The CollisionDetection plugin follows Helios's fail-fast philosophy with comprehensive error handling:
try {
std::vector<uint> collisions = collision.findCollisions(invalid_UUID);
} catch( const helios_runtime_error& e ) {
std::cout << "Collision detection error: " << e.what() << std::endl;
}
GPU Acceleration Control
Control GPU acceleration and monitor system status:
if( collision.isGPUAccelerationEnabled() ){
std::cout << "GPU acceleration is active" << std::endl;
}
collision.disableGPUAcceleration();
collision.enableGPUAcceleration();
collision.enableMessages();
collision.disableMessages();
Troubleshooting
Common Issues and Solutions
Issue**: Collision detection returns unexpected results
Solution: The BVH is rebuilt automatically when geometry changes. If issues persist, try calling CollisionDetection::rebuildBVH() manually
Issue**: GPU acceleration not working or slower than expected
- Solution: Check CUDA installation, verify GPU memory availability, or disable GPU acceleration for small scenes (<100 primitives)
**Diagnostics**: Enable messages to see "Using GPU/CPU traversal" output
Issue**: Ray tracing produces no hits when geometry is present
- **Solution**: Ensure ray directions are normalized and max_distance is positive (or negative for infinite)
- **Validation**: Check HitResult::hit field and verify ray parameters
Performance Guidelines
For optimal performance:
- **Batch Operations**: Always prefer batch queries over individual queries
- **GPU Acceleration**: Use GPU for large scenes (>1000 primitives) and batch operations
- Memory Management: Monitor BVH memory usage and rebuild only when necessary
- Distance Limits: Use distance-limited queries to reduce computational overhead
std::vector<uint> many_uuids = {UUID1, UUID2, UUID3};
auto batch_collisions = collision.findCollisions(many_uuids);
if( collision.isGPUAccelerationEnabled() && primitive_count > 1000 ){
auto results = collision.castRays(large_ray_batch, &stats);
}
Primitive Slicing Operations
The CollisionDetection plugin provides primitive slicing capabilities for subdividing geometry along planar boundaries or regular grids. This functionality was integrated from the VoxelIntersection plugin and allows for precise spatial discretization of primitives.
Single Primitive Slicing
Use CollisionDetection::slicePrimitive() to slice a primitive along a planar face:
std::vector<vec3> plane_vertices = {
make_vec3(0, 0, 0),
make_vec3(1, 0, 0),
make_vec3(0, 1, 0)
};
std::vector<uint> new_triangles = collisiondetection.slicePrimitive(uuid, plane_vertices, warnings);
Key features**:
- Preserves texture UV coordinates during slicing
- Handles both triangles (3 vertices) and patches (4 vertices)
- Validates area conservation
- Reports warnings for numerical issues
Grid-Based Slicing
Use CollisionDetection::slicePrimitivesUsingGrid() to slice primitives along a regular 3D grid:
vec3 grid_center = make_vec3(0, 0, 0);
vec3 grid_size = make_vec3(10, 10, 10);
int3 grid_divisions = make_int3(5, 5, 5);
std::vector<uint> sliced_primitives = collisiondetection.slicePrimitivesUsingGrid(
primitive_uuids, grid_center, grid_size, grid_divisions
);
auto grid = collisiondetection.getGridCells();
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[i].size(); j++) {
for (int k = 0; k < grid[i][j].size(); k++) {
std::vector<uint> cell_primitives = grid[i][j][k];
std::cout << "Cell [" << i << "," << j << "," << k << "] contains "
<< cell_primitives.size() << " primitives" << std::endl;
}
}
}
std::vector<uint> specific_cell = collisiondetection.getGridIntersections(2, 3, 1);
Grid slicing creates**:
- New triangular primitives from sliced geometry
- "cell_ID" primitive data on each primitive (value = cell index, -1 if outside grid)
- Populated 4D grid_cells structure accessible via CollisionDetection::getGridCells()
Voxel-Primitive Intersection
Use CollisionDetection::calculatePrimitiveVoxelIntersection() to determine which voxel primitives contain which planar primitives:
collisiondetection.calculatePrimitiveVoxelIntersection();
for (
uint voxel_uuid : voxel_primitives) {
if (context.doesPrimitiveDataExist(voxel_uuid, "inside_UUIDs")) {
std::vector<uint> inside_prims;
context.getPrimitiveData(voxel_uuid, "inside_UUIDs", inside_prims);
std::cout << "Voxel " << voxel_uuid << " contains "
<< inside_prims.size() << " primitives" << std::endl;
}
}
Note**: This method uses OpenMP parallelization for CPU execution. The algorithm tests primitive centers against voxel bounding boxes using ray-box intersection.
Grid Intersection Helper Method
For convenience, use CollisionDetection::calculateGridIntersection() which combines slicing with grid population:
collisiondetection.calculateGridIntersection(grid_center, grid_size, grid_divisions);
auto grid = collisiondetection.getGridCells();
Migration from VoxelIntersection Plugin
If migrating code from the deprecated VoxelIntersection plugin:
API Changes**:
- Replace
VoxelIntersection class with CollisionDetection
- All method names remain the same (no API changes needed)
calculatePrimitiveVoxelIntersection() now uses OpenMP instead of CUDA
Method signatures use const references for vector parameters
Example Migration**:
collisiondetection.slicePrimitivesUsingGrid(uuids, center, size, divisions);
Performance Note**: The OpenMP implementation may have different performance characteristics compared to the GPU version. If performance is critical for large datasets, benchmark and consider future GPU optimization.