#include <gvox/gvox.h>
#include "../shared/gvox_octree.hpp"
#include <bit>
#include <variant>
struct OctreeUserState {
GvoxRegionRange range{};
std::vector<uint32_t> voxels;
size_t offset{};
uint32_t max_depth{};
};
using OctreeTempNode = std::variant<OctreeNode::Parent, OctreeNode::Leaf>;
extern "C" void gvox_serialize_adapter_gvox_octree_create(GvoxAdapterContext *ctx, void const * ) {
auto *user_state_ptr = malloc(sizeof(OctreeUserState));
new (user_state_ptr) OctreeUserState();
gvox_adapter_set_user_pointer(ctx, user_state_ptr);
}
extern "C" void gvox_serialize_adapter_gvox_octree_destroy(GvoxAdapterContext *ctx) {
auto &user_state = *static_cast<OctreeUserState *>(gvox_adapter_get_user_pointer(ctx));
user_state.~OctreeUserState();
free(&user_state);
}
extern "C" void gvox_serialize_adapter_gvox_octree_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) {
if ((channel_flags & ~static_cast<uint32_t>(GVOX_CHANNEL_BIT_COLOR)) != 0u) {
gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "The current OctreeNode impl can't support anything other than color");
return;
}
auto &user_state = *static_cast<OctreeUserState *>(gvox_adapter_get_user_pointer(ctx));
user_state.offset = 0;
user_state.range = *range;
auto magic = std::bit_cast<uint64_t>(std::array<char, 8>{'g', 'v', 'o', 'c', 't', 'r', 'e', 'e'});
gvox_output_write(blit_ctx, user_state.offset, sizeof(magic), &magic);
user_state.offset += sizeof(magic);
gvox_output_write(blit_ctx, user_state.offset, sizeof(*range), range);
user_state.offset += sizeof(*range);
user_state.max_depth = ceil_log2(std::max({range->extent.x, range->extent.y, range->extent.z}));
user_state.voxels.resize(range->extent.x * range->extent.y * range->extent.z);
}
void do_output(OctreeUserState &user_state, uint32_t my_output_index, std::vector<OctreeNode> &output, std::vector<std::vector<OctreeTempNode>> const &levels, uint32_t depth, uint32_t max_depth, uint32_t x, uint32_t y, uint32_t z) {
if (depth == max_depth) {
auto sample_child = [&user_state](uint32_t x, uint32_t y, uint32_t z) {
if (x < user_state.range.extent.x &&
y < user_state.range.extent.y &&
z < user_state.range.extent.z) {
auto grid_size_x = user_state.range.extent.x;
auto grid_size_y = user_state.range.extent.y;
auto index = x + y * grid_size_x + z * grid_size_x * grid_size_y;
return user_state.voxels[index];
} else {
return 0u;
}
};
output[my_output_index] = OctreeNode{.leaf = {.color = sample_child(x, y, z)}};
return;
}
auto level_i = max_depth - 1 - depth;
auto grid_size = 1u << depth;
auto const &my_gvox_octree = levels[level_i][x + y * grid_size + z * grid_size * grid_size];
if (std::holds_alternative<OctreeNode::Parent>(my_gvox_octree)) {
auto const &my_node = std::get<OctreeNode::Parent>(my_gvox_octree);
auto children_index = static_cast<uint32_t>(output.size());
output[my_output_index] = OctreeNode{.parent = my_node};
output[my_output_index].parent.child_pointer = children_index;
for (uint32_t child_i = 0; child_i < 8; ++child_i) {
output.push_back(OctreeNode{});
}
for (uint32_t czi = 0; czi < 2; ++czi) {
for (uint32_t cyi = 0; cyi < 2; ++cyi) {
for (uint32_t cxi = 0; cxi < 2; ++cxi) {
auto child_i = cxi + cyi * 2 + czi * 4;
do_output(user_state, children_index + child_i, output, levels, depth + 1, max_depth, x * 2 + cxi, y * 2 + cyi, z * 2 + czi);
}
}
}
} else {
output[my_output_index] = OctreeNode{.leaf = std::get<OctreeNode::Leaf>(my_gvox_octree)};
}
}
extern "C" void gvox_serialize_adapter_gvox_octree_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) {
auto &user_state = *static_cast<OctreeUserState *>(gvox_adapter_get_user_pointer(ctx));
auto levels = std::vector<std::vector<OctreeTempNode>>{};
levels.resize(user_state.max_depth);
for (uint32_t level_i = 0; level_i < user_state.max_depth; ++level_i) {
auto depth = user_state.max_depth - 1 - level_i;
auto grid_size = 1u << depth;
levels[level_i].resize(grid_size * grid_size * grid_size);
for (uint32_t zi = 0; zi < grid_size; ++zi) {
for (uint32_t yi = 0; yi < grid_size; ++yi) {
for (uint32_t xi = 0; xi < grid_size; ++xi) {
if (level_i == 0) {
std::array<uint32_t, 8> children{};
auto sample_child = [&user_state](uint32_t x, uint32_t y, uint32_t z) {
if (x < user_state.range.extent.x &&
y < user_state.range.extent.y &&
z < user_state.range.extent.z) {
auto index = x + y * user_state.range.extent.x + z * user_state.range.extent.x * user_state.range.extent.y;
return user_state.voxels[index];
} else {
return 0u;
}
};
for (uint32_t child_i = 0; child_i < 8; ++child_i) {
children[child_i] = sample_child(xi * 2 + (child_i & 1), yi * 2 + ((child_i / 2) & 1), zi * 2 + ((child_i / 4) & 1));
}
bool is_uniform = true;
for (uint32_t child_i = 1; child_i < 8; ++child_i) {
if (children[child_i] != children[0]) {
is_uniform = false;
break;
}
}
if (is_uniform) {
levels[level_i][xi + yi * grid_size + zi * grid_size * grid_size] = OctreeNode::Leaf{
.color = children[0],
};
} else {
levels[level_i][xi + yi * grid_size + zi * grid_size * grid_size] = OctreeNode::Parent{
.child_pointer = 0,
.leaf_mask = 0xff,
};
}
} else {
std::array<OctreeTempNode, 8> children{};
auto sample_child = [&levels, level_i, grid_size](uint32_t x, uint32_t y, uint32_t z) {
auto index = x + y * grid_size * 2 + z * grid_size * 2 * grid_size * 2;
return levels[level_i - 1][index];
};
for (uint32_t child_i = 0; child_i < 8; ++child_i) {
children[child_i] = sample_child(xi * 2 + (child_i & 1), yi * 2 + ((child_i / 2) & 1), zi * 2 + ((child_i / 4) & 1));
}
bool is_uniform = true;
uint32_t leaf_mask = 0;
for (uint32_t child_i = 0; child_i < 8; ++child_i) {
if (std::holds_alternative<OctreeNode::Parent>(children[child_i])) {
is_uniform = false;
} else {
leaf_mask |= 1 << child_i;
if (children[child_i] != children[0]) {
is_uniform = false;
}
}
}
if (is_uniform) {
levels[level_i][xi + yi * grid_size + zi * grid_size * grid_size] = children[0];
} else {
levels[level_i][xi + yi * grid_size + zi * grid_size * grid_size] = OctreeNode::Parent{
.child_pointer = 0,
.leaf_mask = leaf_mask,
};
}
}
}
}
}
}
auto output = std::vector<OctreeNode>{};
output.push_back(OctreeNode{});
do_output(user_state, 0, output, levels, 0, user_state.max_depth, 0, 0, 0);
auto node_count = static_cast<uint32_t>(output.size());
gvox_output_write(blit_ctx, user_state.offset, sizeof(node_count), &node_count);
user_state.offset += sizeof(node_count);
gvox_output_write(blit_ctx, user_state.offset, output.size() * sizeof(output[0]), output.data());
}
namespace {
void handle_region(OctreeUserState &user_state, GvoxRegionRange const *range, auto user_func) {
for (uint32_t zi = 0; zi < range->extent.z; ++zi) {
for (uint32_t yi = 0; yi < range->extent.y; ++yi) {
for (uint32_t xi = 0; xi < range->extent.x; ++xi) {
auto const pos = GvoxOffset3D{
static_cast<int32_t>(xi) + range->offset.x,
static_cast<int32_t>(yi) + range->offset.y,
static_cast<int32_t>(zi) + range->offset.z,
};
if (pos.x < user_state.range.offset.x ||
pos.y < user_state.range.offset.y ||
pos.z < user_state.range.offset.z ||
pos.x >= user_state.range.offset.x + static_cast<int32_t>(user_state.range.extent.x) ||
pos.y >= user_state.range.offset.y + static_cast<int32_t>(user_state.range.extent.y) ||
pos.z >= user_state.range.offset.z + static_cast<int32_t>(user_state.range.extent.z)) {
continue;
}
auto output_rel_x = static_cast<size_t>(pos.x - user_state.range.offset.x);
auto output_rel_y = static_cast<size_t>(pos.y - user_state.range.offset.y);
auto output_rel_z = static_cast<size_t>(pos.z - user_state.range.offset.z);
auto output_index = static_cast<size_t>(output_rel_x + output_rel_y * user_state.range.extent.x + output_rel_z * user_state.range.extent.x * user_state.range.extent.y);
user_func(output_index, pos);
}
}
}
}
}
extern "C" void gvox_serialize_adapter_gvox_octree_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t ) {
auto &user_state = *static_cast<OctreeUserState *>(gvox_adapter_get_user_pointer(ctx));
handle_region(
user_state, range,
[blit_ctx, &user_state](size_t output_index, GvoxOffset3D const &pos) {
auto const sample_range = GvoxRegionRange{
.offset = pos,
.extent = GvoxExtent3D{1, 1, 1},
};
auto region = gvox_load_region_range(blit_ctx, &sample_range, GVOX_CHANNEL_BIT_COLOR);
auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, GVOX_CHANNEL_ID_COLOR);
if (sample.is_present == 0u) {
sample.data = 0u;
}
user_state.voxels[output_index] = sample.data;
gvox_unload_region_range(blit_ctx, ®ion, &sample_range);
});
}
extern "C" void gvox_serialize_adapter_gvox_octree_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) {
auto &user_state = *static_cast<OctreeUserState *>(gvox_adapter_get_user_pointer(ctx));
handle_region(
user_state, ®ion->range,
[blit_ctx, region, &user_state](size_t output_index, GvoxOffset3D const &pos) {
auto sample = gvox_sample_region(blit_ctx, region, &pos, GVOX_CHANNEL_ID_COLOR);
if (sample.is_present != 0u) {
user_state.voxels[output_index] = sample.data;
}
});
}