61 int total_bins = theta_div * phi_div;
63 depth_values.resize(total_bins, std::numeric_limits<float>::max());
68 bool isCovered(
int theta,
int phi)
const {
73 void setCovered(
int theta,
int phi,
float depth) {
194 std::vector<RayQuery> queries;
210 base_memory += targets.size() *
sizeof(
uint);
229 void addRays(
const std::vector<RayQuery> &queries) {
230 for (
const auto &query: queries) {
246 std::vector<HitResult> all_results;
249 for (
const auto &packet:
packets) {
250 all_results.insert(all_results.end(), packet.results.begin(), packet.results.end());
269 size_t total_memory = 0;
270 for (
const auto &packet:
packets) {
271 total_memory += packet.getMemoryUsage();
313 std::vector<uint>
findCollisions(
const std::vector<uint> &UUIDs,
bool allow_spatial_culling =
true);
322 std::vector<uint>
findCollisions(
const std::vector<uint> &primitive_UUIDs,
const std::vector<uint> &object_IDs,
bool allow_spatial_culling =
true);
333 std::vector<uint>
findCollisions(
const std::vector<uint> &query_UUIDs,
const std::vector<uint> &query_object_IDs,
const std::vector<uint> &target_UUIDs,
const std::vector<uint> &target_object_IDs,
bool allow_spatial_culling =
true);
360 std::vector<HitResult>
castRays(
const std::vector<RayQuery> &ray_queries, RayTracingStats *stats =
nullptr);
413 RayTracingStats *stats =
nullptr);
442 std::vector<HitResult>
castRaysOptimized(
const std::vector<RayQuery> &ray_queries, RayTracingStats *stats =
nullptr);
444#ifdef HELIOS_CUDA_AVAILABLE
452 std::vector<HitResult> castRaysGPU(
const std::vector<RayQuery> &ray_queries, RayTracingStats &stats);
461 bool processRayStream(RayStream &ray_stream, RayTracingStats *stats =
nullptr);
468 size_t soa_memory_bytes = 0;
469 size_t quantized_memory_bytes = 0;
470 float quantized_reduction_percent = 0.0f;
496 std::vector<std::vector<HitResult>>
calculateVoxelPathLengths(
const helios::vec3 &scan_origin,
const std::vector<helios::vec3> &ray_directions,
const std::vector<helios::vec3> &voxel_centers,
const std::vector<helios::vec3> &voxel_sizes);
508 std::vector<HitResult> &hit_results);
539 std::vector<std::vector<std::vector<std::vector<uint>>>>
getGridCells();
602 bool approxSame(
float a,
float b,
float absTol,
float relTol)
const;
692 int optimizeLayout(
const std::vector<uint> &UUIDs,
float learning_rate = 0.01f,
int max_iterations = 1000);
703 std::vector<std::pair<uint, uint>>
findCollisionsWithinDistance(
const std::vector<uint> &query_UUIDs,
const std::vector<uint> &target_UUIDs,
float max_distance);
788 void buildBVH(
const std::vector<uint> &UUIDs = {});
795 void updateBVH(
const std::vector<uint> &UUIDs,
bool force_rebuild =
false);
870 void registerTree(
uint tree_object_id,
const std::vector<uint> &tree_primitives);
970 void getBVHStatistics(
size_t &node_count,
size_t &leaf_count,
size_t &max_depth)
const;
976 static int selfTest(
int argc,
char **argv);
983 bool gpu_acceleration_enabled;
990 volatile int *cancel_flag =
nullptr;
997 struct CachedPrimitive {
999 std::vector<helios::vec3> vertices;
1003 uint UUID = 0xFFFFFFFFu;
1007 const std::vector<std::vector<bool>> *transparency_mask =
nullptr;
1011 std::vector<helios::vec2> uv;
1015 CachedPrimitive(
helios::PrimitiveType t,
const std::vector<helios::vec3> &v) : type(t), vertices(v) {
1022 std::unordered_map<uint, CachedPrimitive> primitive_cache;
1028 std::vector<CachedPrimitive> primitive_cache_dense;
1033 void buildPrimitiveCache();
1041 void rebuildDensePrimitiveCache();
1049 void ensurePrimitiveCacheCurrent();
1054 HitResult intersectPrimitiveThreadSafe(
const helios::vec3 &origin,
const helios::vec3 &direction,
uint primitive_id,
float max_distance);
1064 HitResult intersectCachedPrimitive(
const helios::vec3 &origin,
const helios::vec3 &direction,
const CachedPrimitive &cached,
float max_distance)
const;
1078 [[nodiscard]]
bool isHitTexelOpaque(
const CachedPrimitive &cached,
const helios::vec3 &hit_point)
const;
1097 uint primitive_start;
1098 uint primitive_count;
1101 BVHNode() : aabb_min(0, 0, 0), aabb_max(0, 0, 0), left_child(0xFFFFFFFF), right_child(0xFFFFFFFF), primitive_start(0), primitive_count(0), is_leaf(false) {
1109 struct BVHNodesSoA {
1111 std::vector<helios::vec3> aabb_mins;
1112 std::vector<helios::vec3> aabb_maxs;
1113 std::vector<uint32_t> left_children;
1114 std::vector<uint32_t> right_children;
1117 std::vector<uint32_t> primitive_starts;
1118 std::vector<uint32_t> primitive_counts;
1119 std::vector<uint8_t> is_leaf_flags;
1122 size_t node_count = 0;
1124 BVHNodesSoA() =
default;
1129 void reserve(
size_t capacity) {
1130 aabb_mins.reserve(capacity);
1131 aabb_maxs.reserve(capacity);
1132 left_children.reserve(capacity);
1133 right_children.reserve(capacity);
1134 primitive_starts.reserve(capacity);
1135 primitive_counts.reserve(capacity);
1136 is_leaf_flags.reserve(capacity);
1145 left_children.clear();
1146 right_children.clear();
1147 primitive_starts.clear();
1148 primitive_counts.clear();
1149 is_leaf_flags.clear();
1156 [[nodiscard]]
size_t getMemoryUsage()
const {
1157 return (aabb_mins.size() + aabb_maxs.size()) *
sizeof(
helios::vec3) + (left_children.size() + right_children.size() + primitive_starts.size() + primitive_counts.size()) *
sizeof(uint32_t) + is_leaf_flags.size() *
sizeof(uint8_t);
1163 std::vector<BVHNode> bvh_nodes;
1166 size_t next_available_node_index;
1169 BVHNodesSoA bvh_nodes_soa;
1175 std::vector<uint> primitive_indices;
1178 std::unordered_map<uint, std::pair<helios::vec3, helios::vec3>> primitive_aabbs_cache;
1181 std::unordered_set<uint> dirty_primitive_cache;
1184 std::vector<std::vector<std::vector<std::vector<uint>>>> grid_cells;
1194 std::vector<std::vector<std::vector<int>>> voxel_ray_counts;
1197 std::vector<std::vector<std::vector<int>>> voxel_transmitted;
1200 std::vector<std::vector<std::vector<float>>> voxel_path_lengths;
1203 std::vector<std::vector<std::vector<int>>> voxel_hit_before;
1206 std::vector<std::vector<std::vector<int>>> voxel_hit_after;
1209 std::vector<std::vector<std::vector<int>>> voxel_hit_inside;
1212 std::vector<std::vector<std::vector<std::vector<float>>>> voxel_individual_path_lengths;
1217 std::vector<int> voxel_ray_counts_flat;
1218 std::vector<int> voxel_transmitted_flat;
1219 std::vector<float> voxel_path_lengths_flat;
1220 std::vector<int> voxel_hit_before_flat;
1221 std::vector<int> voxel_hit_after_flat;
1222 std::vector<int> voxel_hit_inside_flat;
1225 std::vector<float> voxel_individual_path_lengths_flat;
1226 std::vector<size_t> voxel_individual_path_offsets;
1227 std::vector<size_t> voxel_individual_path_counts;
1230 bool use_flat_arrays;
1233 bool voxel_data_initialized;
1243 float max_collision_distance;
1246 std::unordered_map<uint, helios::vec3> primitive_centroids_cache;
1254 uint *d_primitive_indices;
1257 int *d_primitive_types;
1260 void *d_primitive_vertices;
1263 uint *d_vertex_offsets;
1271 uint *d_mask_offsets;
1281 bool d_gpu_has_masks;
1284 int d_gpu_node_count;
1285 int d_gpu_primitive_count;
1286 int d_gpu_total_vertex_count;
1289 bool gpu_memory_allocated;
1292 std::set<uint> last_processed_uuids;
1295 std::set<uint> last_processed_deleted_uuids;
1298 std::set<uint> static_geometry_cache;
1301 std::set<uint> last_bvh_geometry;
1310 bool automatic_bvh_rebuilds;
1313 mutable bool batch_mode_skip_bvh_check;
1318 bool hierarchical_bvh_enabled;
1321 std::vector<BVHNode> static_bvh_nodes;
1324 std::vector<uint> static_bvh_primitives;
1327 bool static_bvh_valid;
1330 std::set<uint> last_static_bvh_geometry;
1333 void updateHierarchicalBVH(
const std::set<uint> &requested_geometry,
bool force_rebuild);
1341 bool tree_based_bvh_enabled;
1344 float tree_isolation_distance;
1347 std::unordered_map<uint, uint> object_to_tree_map;
1350 std::vector<uint> static_obstacle_primitives;
1353 struct ObstacleSpatialGrid {
1355 std::unordered_map<int64_t, std::vector<uint>> grid_cells;
1357 [[nodiscard]] int64_t getGridKey(
float x,
float y)
const {
1358 auto grid_x =
static_cast<int32_t
>(std::floor(x / cell_size));
1359 auto grid_y =
static_cast<int32_t
>(std::floor(y / cell_size));
1360 return (
static_cast<int64_t
>(grid_x) << 32) |
static_cast<uint32_t
>(grid_y);
1363 [[nodiscard]] std::vector<uint> getRelevantObstacles(
const helios::vec3 &position,
float radius)
const;
1366 mutable ObstacleSpatialGrid obstacle_spatial_grid;
1367 bool obstacle_spatial_grid_initialized;
1386 float angular_distance;
1388 std::vector<int> sample_indices;
1394 struct SpatialHashGrid {
1399 std::vector<std::vector<Cell>> grid;
1400 int theta_resolution;
1405 explicit SpatialHashGrid(
int theta_res = 32,
int phi_res = 16) : theta_resolution(theta_res), phi_resolution(phi_res) {
1406 grid.resize(theta_resolution);
1407 for (
auto &row: grid) {
1408 row.resize(phi_resolution);
1410 theta_step =
M_PI / theta_resolution;
1411 phi_step = 2.0f *
M_PI / phi_resolution;
1415 for (
auto &row: grid) {
1416 for (
auto &cell: row) {
1417 cell.sample_indices.clear();
1422 [[nodiscard]] std::pair<int, int> getGridIndex(
const helios::vec3 &direction)
const {
1424 float theta = acosf(std::max(-1.0f, std::min(1.0f, direction.
z)));
1425 float phi = atan2f(direction.
y, direction.
x);
1429 int theta_idx = std::min((
int) (theta / theta_step), theta_resolution - 1);
1430 int phi_idx = std::min((
int) (phi / phi_step), phi_resolution - 1);
1432 return {theta_idx, phi_idx};
1435 void addSample(
size_t sample_idx,
const helios::vec3 &direction) {
1436 auto [theta_idx, phi_idx] = getGridIndex(direction);
1437 grid[theta_idx][phi_idx].sample_indices.push_back(sample_idx);
1440 [[nodiscard]] std::vector<size_t> getNearbyIndices(
const helios::vec3 &direction,
int radius = 1)
const {
1441 auto [center_theta, center_phi] = getGridIndex(direction);
1442 std::vector<size_t> nearby_indices;
1444 for (
int dt = -radius; dt <= radius; dt++) {
1445 for (
int dp = -radius; dp <= radius; dp++) {
1446 int theta_idx = center_theta + dt;
1447 int phi_idx = (center_phi + dp + phi_resolution) % phi_resolution;
1449 if (theta_idx >= 0 && theta_idx < theta_resolution) {
1450 const auto &cell = grid[theta_idx][phi_idx];
1451 nearby_indices.insert(nearby_indices.end(), cell.sample_indices.begin(), cell.sample_indices.end());
1456 return nearby_indices;
1464 uint tree_object_id;
1467 std::vector<BVHNode> nodes;
1468 std::vector<uint> primitive_indices;
1469 BVHNodesSoA soa_structure;
1472 TreeBVH() : tree_object_id(0), tree_center(0, 0, 0), tree_radius(0), soa_dirty(true) {
1477 primitive_indices.clear();
1478 soa_structure = BVHNodesSoA();
1484 std::unordered_map<uint, TreeBVH> tree_bvh_map;
1494 void ensureBVHCurrent();
1515 void buildBVHRecursive(
uint node_index,
size_t primitive_start,
size_t primitive_count,
int depth);
1525#ifdef HELIOS_CUDA_AVAILABLE
1578 void traverseBVHSIMD(
const helios::vec3 *ray_origins,
const helios::vec3 *ray_directions,
int count, HitResult *results);
1583 void traverseBVHSIMDImpl(
const helios::vec3 *ray_origins,
const helios::vec3 *ray_directions,
int count, HitResult *results);
1634 bool findNearestRayIntersection(
const helios::vec3 &origin,
const helios::vec3 &direction,
const std::set<uint> &candidate_UUIDs,
float &nearest_distance,
float max_distance = -1.0f);
1644 std::vector<helios::vec3> sampleDirectionsInCone(
const helios::vec3 &apex,
const helios::vec3 ¢ral_axis,
float half_angle,
int num_samples);
1657 std::vector<Gap> detectGapsInCone(
const helios::vec3 &apex,
const helios::vec3 ¢ral_axis,
float half_angle,
float height,
int num_samples);
1668 std::vector<uint> getCandidatePrimitivesInCone(
const helios::vec3 &apex,
const helios::vec3 ¢ral_axis,
float half_angle,
float height);
1679 std::vector<uint> getCandidatesUsingSpatialGrid(
const Cone &cone,
const helios::vec3 &apex,
const helios::vec3 ¢ral_axis,
float half_angle,
float height);
1687 float calculateGapAngularSize(
const std::vector<RaySample> &gap_samples,
const helios::vec3 ¢ral_axis);
1694 void scoreGapsByFishEyeMetric(std::vector<Gap> &gaps,
const helios::vec3 ¢ral_axis);
1719 void calculateVoxelRayPathLengths_CPU(
const std::vector<helios::vec3> &ray_origins,
const std::vector<helios::vec3> &ray_directions);
1726 bool calculateVoxelRayPathLengths_GPU(
const std::vector<helios::vec3> &ray_origins,
const std::vector<helios::vec3> &ray_directions);
1733 bool validateVoxelIndices(
const helios::int3 &ijk)
const;
1749 std::vector<std::pair<helios::int3, float>> traverseVoxelGrid(
const helios::vec3 &ray_origin,
const helios::vec3 &ray_direction)
const;
1758 inline size_t flatIndex(
int i,
int j,
int k)
const {
1759 return static_cast<size_t>(i) *
static_cast<size_t>(voxel_grid_divisions.
y) *
static_cast<size_t>(voxel_grid_divisions.
z) +
static_cast<size_t>(j) *
static_cast<size_t>(voxel_grid_divisions.
z) +
static_cast<size_t>(k);
1767 inline size_t flatIndex(
const helios::int3 &ijk)
const {
1768 return flatIndex(ijk.
x, ijk.
y, ijk.
z);
1771#ifdef HELIOS_CUDA_AVAILABLE
1775 void allocateGPUMemory();
1780 void freeGPUMemory();
1785 void transferBVHToGPU();
1808 void buildGPUGeometrySoA(std::vector<int> &primitive_types, std::vector<float> &primitive_vertices_xyz, std::vector<unsigned int> &vertex_offsets, std::vector<unsigned char> &mask_data, std::vector<unsigned int> &mask_offsets,
1809 std::vector<int> &mask_sizes, std::vector<int> &mask_IDs, std::vector<float> &uv_data, std::vector<int> &uv_IDs);
1822 [[nodiscard]]
bool shouldUseGPU(
size_t ray_count)
const;
1827 void markBVHDirty();
1835 void incrementalUpdateBVH(
const std::set<uint> &added_geometry,
const std::set<uint> &removed_geometry,
const std::set<uint> &final_geometry);
1841 void updatePrimitiveAABBCache(
uint uuid);
1847 void optimizedRebuildBVH(
const std::set<uint> &final_geometry);
1854 bool validateUUIDs(
const std::vector<uint> &UUIDs)
const;
1872 void castRaysCPU(
const std::vector<RayQuery> &ray_queries, std::vector<HitResult> &results, RayTracingStats &stats);
1874#ifdef HELIOS_CUDA_AVAILABLE
1881 void castRaysGPU(
const std::vector<RayQuery> &ray_queries, std::vector<HitResult> &results, RayTracingStats &stats);
1890 void ensureOptimizedBVH();
1895 std::vector<HitResult>
castRaysSoA(
const std::vector<RayQuery> &ray_queries, RayTracingStats &stats);
1900 HitResult castRaySoATraversal(
const RayQuery &query, RayTracingStats &stats);
1910 void 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, RayTracingStats &stats);
1916 HitResult castRayBVHTraversal(
const RayQuery &query);
1921 inline bool aabbIntersectSoA(
const helios::vec3 &ray_origin,
const helios::vec3 &ray_direction,
float max_distance,
size_t node_index)
const;
1929 float aabbEntryDistanceSoA(
const helios::vec3 &ray_origin,
const helios::vec3 &ray_direction,
size_t node_index)
const;
1940 HitResult intersectPrimitive(
const RayQuery &query,
uint primitive_id);
1961 int calculateOptimalBinCount(
float cone_half_angle,
int geometry_count);
1969 std::vector<uint> filterPrimitivesParallel(
const Cone &cone,
const std::vector<uint> &primitive_uuids);
1977 void projectGeometryToBins(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins);
1985 std::vector<Gap> findGapsInCoverageMap(
const AngularBins &bins,
const Cone &cone);
1996 bool sphericalCoordsToBinIndices(
float theta,
float phi,
const AngularBins &bins,
int &theta_bin,
int &phi_bin);
2006 float cartesianToSphericalCone(
const helios::vec3 &vector,
const helios::vec3 &cone_axis,
float &theta,
float &phi);
2014 void projectGeometryToBinsSerial(
const Cone &cone,
const std::vector<uint> &filtered_uuids, AngularBins &bins);
2025 Gap floodFillGap(
const AngularBins &bins,
int start_theta,
int start_phi, std::vector<std::vector<bool>> &visited,
const Cone &cone);
2035 float calculateBinSolidAngle(
int theta_bin,
int phi_bin,
const AngularBins &bins,
float cone_half_angle);
2045 helios::vec3 binIndicesToCartesian(
int theta_bin,
int phi_bin,
const AngularBins &bins,
const Cone &cone);
2055 void addUnoccupiedNeighbors(
int theta,
int phi,
const AngularBins &bins, std::vector<std::vector<bool>> &visited, std::queue<std::pair<int, int>> &queue);