40 voxel::VoxelClass classification;
41 uint8_t palette_index;
43 uint8_t exposure = voxel::k_all_faces_exposed;
48template<
typename Callback>
50auto invokeVoxelCallback(Callback& callback,
const VoxelCandidate& candidate) ->
bool {
51 if constexpr (std::is_same_v<std::invoke_result_t<Callback&, const VoxelCandidate&>,
bool>) {
52 return callback(candidate);
61template<
typename Callback>
64 glm::ivec3 first = glm::ivec3(glm::floor(aabb.min / voxel::k_voxel_size));
65 glm::ivec3 last = glm::ivec3(glm::ceil(aabb.max / voxel::k_voxel_size)) - 1;
67 const glm::ivec3 voxel_dims = glm::ivec3(context.volume.voxelDims());
68 if (glm::any(glm::lessThan(last, glm::ivec3(0))) || glm::any(glm::greaterThanEqual(first, voxel_dims))) {
71 first = glm::clamp(first, glm::ivec3(0), voxel_dims - 1);
72 last = glm::clamp(last, glm::ivec3(0), voxel_dims - 1);
73 if (first.x > last.x || first.y > last.y || first.z > last.z) {
77 const glm::ivec3 first_brick = first >> 3;
78 const glm::ivec3 last_brick = last >> 3;
79 const glm::ivec3 brick_dims = glm::ivec3(context.volume.brickDims());
80 const auto brick_dim =
static_cast<int32_t
>(voxel::k_brick_dim);
84 const auto process_brick = [&](glm::ivec3 brick) {
85 const voxel::BrickEntry entry = context.volume.entryAt(brick);
86 if (entry.tag() == voxel::BrickTag::empty) {
90 const std::span<const voxel::SurfaceVoxel> surface = context.surface.brickSurface(brick);
91 if (surface.empty()) {
96 const uint32_t brick_slot =
static_cast<uint32_t
>(brick.x) + (
static_cast<uint32_t
>(brick.y) * brick_dims.x) +
97 (
static_cast<uint32_t
>(brick.z) * brick_dims.x * brick_dims.y);
99 const glm::ivec3 brick_base = brick * brick_dim;
100 const auto emit = [&](
const voxel::SurfaceVoxel& surface_voxel, glm::ivec3 global_voxel) ->
bool {
101 const uint8_t palette_index = context.volume.materialAt(global_voxel);
103 const voxel::PhysicalMaterial& material = context.materials.materials[material_index];
104 if (not material.collides()) {
108 const glm::vec3 min = glm::vec3(global_voxel) * voxel::k_voxel_size;
109 const glm::vec3 max = min + glm::vec3(voxel::k_voxel_size);
111 return _detail::invokeVoxelCallback(
114 .voxel = global_voxel,
116 .brick_slot = brick_slot,
120 .normal_index = voxel::normalOf(surface_voxel.classification),
121 .classification = voxel::classOf(surface_voxel.classification),
122 .palette_index = palette_index,
123 .material = &material,
124 .exposure = surface_voxel.exposure,
129 const glm::ivec3 clipped_min = glm::max(first, brick_base);
130 const glm::ivec3 clipped_max = glm::min(last, brick_base + (brick_dim - 1));
131 const glm::ivec3 clipped_dims = clipped_max - clipped_min + 1;
132 const auto clipped_voxels =
133 static_cast<size_t>(clipped_dims.x) *
static_cast<size_t>(clipped_dims.y) *
static_cast<size_t>(clipped_dims.z);
135 if (clipped_voxels >= surface.size()) {
136 for (
const voxel::SurfaceVoxel& surface_voxel : surface) {
137 const voxel::BrickCoord local = voxel::localFromIndex(surface_voxel.
local_index);
138 const glm::ivec3 global_voxel =
139 brick_base + glm::ivec3 {
static_cast<int32_t
>(local.x),
static_cast<int32_t
>(local.y),
static_cast<int32_t
>(local.z)};
141 if (glm::any(glm::lessThan(global_voxel, first)) || glm::any(glm::greaterThan(global_voxel, last))) {
144 if (not emit(surface_voxel, global_voxel)) {
152 for (int32_t vz = clipped_min.z; vz <= clipped_max.z; ++vz) {
153 for (int32_t vy = clipped_min.y; vy <= clipped_max.y; ++vy) {
154 for (int32_t vx = clipped_min.x; vx <= clipped_max.x; ++vx) {
155 const auto local_index =
static_cast<uint16_t
>(
156 (
static_cast<uint32_t
>(vz - brick_base.z) << 6u) | (
static_cast<uint32_t
>(vy - brick_base.y) << 3u) |
157 static_cast<uint32_t
>(vx - brick_base.x)
160 const auto found = std::lower_bound(
161 surface.begin(), surface.end(), local_index, [](
const voxel::SurfaceVoxel& candidate, uint16_t index) {
162 return candidate.local_index < index;
165 if (found == surface.end() || found->local_index != local_index) {
168 if (not emit(*found, glm::ivec3 {vx, vy, vz})) {
177 const uint64_t box_bricks =
static_cast<uint64_t
>(last_brick.x - first_brick.x + 1) *
178 static_cast<uint64_t
>(last_brick.y - first_brick.y + 1) *
179 static_cast<uint64_t
>(last_brick.z - first_brick.z + 1);
180 const uint64_t populated_bricks =
static_cast<uint64_t
>(context.surface.populatedBrickCount());
182 if (populated_bricks < box_bricks) {
183 context.surface.forEachPopulatedBrick([&](glm::ivec3 brick) {
187 if (glm::any(glm::lessThan(brick, first_brick)) || glm::any(glm::greaterThan(brick, last_brick))) {
190 process_brick(brick);
193 for (int32_t z = first_brick.z; z <= last_brick.z && not stopped; ++z) {
194 for (int32_t y = first_brick.y; y <= last_brick.y && not stopped; ++y) {
195 for (int32_t x = first_brick.x; x <= last_brick.x && not stopped; ++x) {
196 process_brick({x, y, z});
204 float distance = 0.0f;
205 glm::vec3 normal = {};
206 glm::ivec3 voxel = {};
207 uint8_t palette_index = voxel::k_empty_palette_index;
212inline constexpr float k_ray_tie_epsilon = 1.0e-6f;
215inline auto rayTieSlack(
float t) ->
float {
216 return k_ray_tie_epsilon * std::max(1.0f, std::abs(t));
226template<
typename OnEntry>
228 const VoxelQueryContext& context,
const AABB& bounds, glm::vec3 origin, glm::vec3 direction,
float max_distance,
229 bool hit_passable, OnEntry&& on_entry
231 constexpr float k_infinity = std::numeric_limits<float>::infinity();
232 constexpr auto k_brick =
static_cast<int32_t
>(voxel::k_brick_dim);
233 constexpr float k_voxel = voxel::k_voxel_size;
236 const glm::ivec3 dims = glm::ivec3(volume.voxelDims());
239 const glm::vec3 start = origin / k_voxel;
240 const glm::vec3 box_min = glm::max(glm::floor(bounds.min / k_voxel - 1.0e-3f), glm::vec3(0.0f));
241 const glm::vec3 box_max = glm::min(glm::ceil(bounds.max / k_voxel + 1.0e-3f), glm::vec3(dims));
242 float limit = max_distance / k_voxel;
244 std::array<float, 3> entry_times {-k_infinity, -k_infinity, -k_infinity};
245 glm::vec3 inverse(0.0f);
246 glm::vec3 sign(0.0f);
247 float t_exit = k_infinity;
249 for (
int axis = 0; axis < 3; ++axis) {
250 if (direction[axis] == 0.0f) {
251 if (start[axis] < box_min[axis] || start[axis] > box_max[axis]) {
257 inverse[axis] = 1.0f / direction[axis];
258 sign[axis] = direction[axis] > 0.0f ? 1.0f : -1.0f;
259 const float t0 = (box_min[axis] - start[axis]) * inverse[axis];
260 const float t1 = (box_max[axis] - start[axis]) * inverse[axis];
261 entry_times[axis] = std::min(t0, t1);
262 t_exit = std::min(t_exit, std::max(t0, t1));
266 float t = -k_infinity;
268 for (
int candidate = 0; candidate < 3; ++candidate) {
269 if (entry_times[candidate] > t) {
270 t = entry_times[candidate];
276 uint32_t crossed = 0;
281 for (
int candidate = 0; candidate < 3; ++candidate) {
282 if (sign[candidate] != 0.0f && entry_times[candidate] >= t - _detail::rayTieSlack(t)) {
283 crossed |= 1u << candidate;
289 const auto leave = [&](glm::ivec3 cell_min, int32_t size) ->
bool {
290 std::array<float, 3> plane_times {k_infinity, k_infinity, k_infinity};
291 float next = k_infinity;
292 int next_axis = axis;
293 for (
int candidate = 0; candidate < 3; ++candidate) {
294 if (sign[candidate] == 0.0f) {
297 const float plane =
static_cast<float>(sign[candidate] > 0.0f ? cell_min[candidate] + size : cell_min[candidate]);
298 plane_times[candidate] = (plane - start[candidate]) * inverse[candidate];
299 if (plane_times[candidate] < next) {
300 next = plane_times[candidate];
301 next_axis = candidate;
304 if (!std::isfinite(next)) {
309 for (
int candidate = 0; candidate < 3; ++candidate) {
310 if (plane_times[candidate] <= next + _detail::rayTieSlack(next)) {
311 crossed |= 1u << candidate;
314 t = std::max(next, t);
319 const auto collides = [&](uint8_t palette_index) {
321 return context.materials.materials[material].collides();
325 const int32_t max_steps = (3 * (dims.x + dims.y + dims.z)) + 16;
326 bool in_solid =
false;
328 for (int32_t step = 0; step < max_steps; ++step) {
329 if (t > std::min(t_exit, limit)) {
333 const glm::vec3 position = start + (direction * t);
335 for (
int candidate = 0; candidate < 3; ++candidate) {
336 if ((crossed & (1u << candidate)) != 0) {
337 const auto plane =
static_cast<int32_t
>(std::lround(position[candidate]));
338 cell[candidate] = sign[candidate] > 0.0f ? plane : plane - 1;
339 }
else if (sign[candidate] < 0.0f) {
341 cell[candidate] =
static_cast<int32_t
>(std::ceil(position[candidate])) - 1;
343 cell[candidate] =
static_cast<int32_t
>(std::floor(position[candidate]));
346 if (glm::any(glm::lessThan(cell, glm::ivec3(0))) || glm::any(glm::greaterThanEqual(cell, dims))) {
350 const glm::ivec3 brick = cell >> 3;
351 const glm::ivec3 brick_min = brick * k_brick;
354 if (entry.tag() == voxel::BrickTag::empty) {
356 if (!leave(brick_min, k_brick)) {
364 uint8_t palette_index = voxel::k_empty_palette_index;
366 palette_index =
static_cast<uint8_t
>(entry.
payload());
369 const glm::uvec3 local = glm::uvec3(cell - brick_min);
370 if (occupancy !=
nullptr && voxel::isSolid(*occupancy, local.x, local.y, local.z)) {
371 palette_index = volume.materialAt(cell);
375 const bool solid = palette_index != voxel::k_empty_palette_index && (hit_passable || collides(palette_index));
376 if (solid && !in_solid) {
377 glm::vec3 normal = -direction;
379 normal = glm::vec3(0.0f);
380 normal[axis] = -sign[axis];
383 on_entry(
VoxelRayEntry {.distance = t * k_voxel, .normal = normal, .voxel = cell, .palette_index = palette_index});
384 limit = std::min(limit, reach / k_voxel);
388 if (!(uniform ? leave(brick_min, k_brick) : leave(cell, 1))) {
Axis-aligned bounds node.
Keyed by brick slot not pool id since uniform bricks have no pool id and shared bricks differ per ins...
Definition surface.hpp:174
Definition voxel_volume.hpp:21
auto occupancyPointer(glm::ivec3 brick) const noexcept -> const BrickOccupancy *
Null when the brick holds nothing.
Definition voxel_volume.cpp:169
auto resolveMaterialIndex(const Palette &palette, const MaterialLibrary &library, uint8_t palette_index) noexcept -> uint32_t
The library must not be empty.
Definition palette.hpp:149
Definition voxel_query.hpp:32
Definition voxel_query.hpp:25
Definition voxel_query.hpp:203
Definition voxel_constants.hpp:44
constexpr auto payload() const noexcept -> uint32_t
Definition voxel_constants.hpp:59
One 64-bit word per z-slice with bit y * 8 + x.
Definition brick.hpp:18
Definition palette.hpp:143
Entry 0 is the empty voxel and must stay all zero.
Definition palette.hpp:59
Definition palette.hpp:94
uint16_t local_index
localIndex which is also the occupancy bit index
Definition surface.hpp:121
@ uniform
The payload is a palette index not a brick id.
Definition voxel_constants.hpp:34
void queryVoxelRay(const VoxelQueryContext &context, const AABB &bounds, glm::vec3 origin, glm::vec3 direction, float max_distance, bool hit_passable, OnEntry &&on_entry)
Definition voxel_query.hpp:227