From f399b95bb8d0a118ed66ceb395a020d46a5d4b05 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 10 Feb 2023 08:54:30 -0700 Subject: [PATCH] light: add octree experimental version of lightgrid lump --- include/light/light.hh | 1 + light/light.cc | 281 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 280 insertions(+), 2 deletions(-) diff --git a/include/light/light.hh b/include/light/light.hh index fe447e7f..ecf916fc 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -240,6 +240,7 @@ enum class visapprox_t enum class lightgrid_format_t { UNIFORM, CLUSTER, + OCTREE }; // diff --git a/light/light.cc b/light/light.cc index 6aefbd6b..5c05790d 100644 --- a/light/light.cc +++ b/light/light.cc @@ -318,7 +318,7 @@ light_settings::light_settings() lightgrid{this, "lightgrid", false, &experimental_group, "experimental LIGHTGRID bspx lump"}, lightgrid_dist{this, "lightgrid_dist", 32.f, 32.f, 32.f, &experimental_group, "distance between lightgrid sample points, in world units. controls lightgrid size."}, lightgrid_format{this, "lightgrid_format", lightgrid_format_t::CLUSTER, - {{"cluster", lightgrid_format_t::CLUSTER}, {"uniform", lightgrid_format_t::UNIFORM}}, &experimental_group, "lightgrid BSPX lump to use"}, + {{"cluster", lightgrid_format_t::CLUSTER}, {"uniform", lightgrid_format_t::UNIFORM}, {"octree", lightgrid_format_t::OCTREE}}, &experimental_group, "lightgrid BSPX lump to use"}, dirtdebug{this, {"dirtdebug", "debugdirt"}, [&](source) { @@ -1099,6 +1099,10 @@ static void LightGrid(bspdata_t *bspdata) occlusion[sample_index] = occluded; }); + auto get_grid_index = [&](int x, int y, int z) -> int { + return (grid_size[0] * grid_size[1] * z) + (grid_size[0] * y) + x; + }; + // the maximum used styles across the map. const uint8_t num_styles = [&](){ int result = 0; @@ -1199,7 +1203,7 @@ static void LightGrid(bspdata_t *bspdata) for (int z = cm[2]; z < (cm[2] + cs[2]); ++z) { for (int y = cm[1]; y < (cm[1] + cs[1]); ++y) { for (int x = cm[0]; x < (cm[0] + cs[0]); ++x) { - int sample_index = (grid_size[0] * grid_size[1] * z) + (grid_size[0] * y) + x; + int sample_index = get_grid_index(x, y, z); if (occlusion[sample_index]) { str <= static_cast(0xff); @@ -1222,6 +1226,279 @@ static void LightGrid(bspdata_t *bspdata) bspdata->bspx.transfer("LIGHTGRID_PERCLUSTER", std::move(vec)); } + + // octree lump + if (light_options.lightgrid_format.value() == lightgrid_format_t::OCTREE) + { + /** + * returns the octant index in [0..7] + */ + auto child_index = [](qvec3i division_point, qvec3i test_point) -> int { + int sign[3]; + for (int i = 0; i < 3; ++i) + sign[i] = (test_point[i] >= division_point[i]); + + return (4 * sign[0]) + (2 * sign[1]) + (sign[2]); + }; + + Q_assert(child_index({1,1,1}, {2,2,2}) == 7); + Q_assert(child_index({1,1,1}, {1,1,0}) == 6); + Q_assert(child_index({1,1,1}, {1,0,1}) == 5); + Q_assert(child_index({1,1,1}, {1,0,0}) == 4); + Q_assert(child_index({1,1,1}, {0,1,1}) == 3); + Q_assert(child_index({1,1,1}, {0,1,0}) == 2); + Q_assert(child_index({1,1,1}, {0,0,1}) == 1); + Q_assert(child_index({1,1,1}, {0,0,0}) == 0); + + /** + * returns octant index `i`'s mins and size + */ + auto get_octant = [](int i, qvec3i mins, qvec3i size, qvec3i division_point) -> std::tuple { + qvec3i child_mins; + qvec3i child_size; + for (int axis = 0; axis < 3; ++axis) { + int bit; + if (axis == 0) { + bit = 4; + } else if (axis == 1) { + bit = 2; + } else { + bit = 1; + } + + if (i & bit) { + child_mins[axis] = division_point[axis]; + child_size[axis] = mins[axis] + size[axis] - division_point[axis]; + } else { + child_mins[axis] = mins[axis]; + child_size[axis] = division_point[axis] - mins[axis]; + } + } + return {child_mins, child_size}; + }; + + Q_assert(get_octant(0, {0,0,0}, {2,2,2}, {1,1,1}) == (std::tuple{{0,0,0}, {1,1,1}})); + Q_assert(get_octant(7, {0,0,0}, {2,2,2}, {1,1,1}) == (std::tuple{{1,1,1}, {1,1,1}})); + + /** + * given a bounding box, selects the division point. + */ + auto get_division_point = [](qvec3i mins, qvec3i size) -> qvec3i { + return mins + (size / 2); + }; + + auto is_all_occluded = [&](qvec3i mins, qvec3i size) -> bool { + for (int z = mins[2]; z < (mins[2] + size[2]); ++z) { + for (int y = mins[1]; y < (mins[1] + size[1]); ++y) { + for (int x = mins[0]; x < (mins[0] + size[0]); ++x) { + int sample_index = get_grid_index(x, y, z); + if (!occlusion[sample_index]) { + return false; + } + } + } + } + return true; + }; + + constexpr int MAX_DEPTH = 5; + // if any axis is fewer than this many grid points, don't bother subdividing further, just create a leaf + constexpr int MIN_NODE_DIMENSION = 4; + + // if set, it's an index in the leafs array + constexpr uint32_t FLAG_LEAF = 1 << 31; + constexpr uint32_t FLAG_OCCLUDED = 1 << 30; + constexpr uint32_t FLAGS = (FLAG_LEAF | FLAG_OCCLUDED); + // if neither flags are set, it's a node index + + struct octree_node { + qvec3i division_point; + std::array children; + }; + + struct octree_leaf { + qvec3i mins, size; + }; + + std::vector octree_nodes; + std::vector octree_leafs; + + int occluded_cells = 0; + + /** + * - inserts either a node or leaf + * - returns one of: + * - FLAG_OCCLUDED if the entire bounds is occluded + * - (FLAG_LEAF | leaf_num) for a leaf - a literal chunk of grid samples + * - otherwise, it's a node index + */ + std::function build_octree; + build_octree = [&](qvec3i mins, qvec3i size, int depth) -> uint32_t { + assert(size[0] > 0); + assert(size[1] > 0); + assert(size[2] > 0); + + // special case: fully occluded leaf, just represented as a flag bit + if (is_all_occluded(mins, size)) { + occluded_cells += size[0] * size[1] * size[2]; + return FLAG_OCCLUDED; + } + + // decide whether we are creating a regular leaf or a node? + bool make_leaf = false; + if (size[0] < MIN_NODE_DIMENSION || size[1] < MIN_NODE_DIMENSION || size[2] < MIN_NODE_DIMENSION) + make_leaf = true; + if (depth == MAX_DEPTH) + make_leaf = true; + + if (make_leaf) { + // make a leaf + const uint32_t leafnum = static_cast(octree_leafs.size()); + octree_leafs.push_back({ + .mins = mins, .size = size + }); + return FLAG_LEAF | leafnum; + } + + // make a node + + const qvec3i division_point = get_division_point(mins, size); + + // create the 8 child nodes/leafs recursively, store the returned indices + std::array children; + for (int i = 0; i < 8; ++i) { + // figure out the mins/size of this child + auto [child_mins, child_size] = get_octant(i, mins, size, division_point); + children[i] = build_octree(child_mins, child_size, depth + 1); + } + + // insert the node + const uint32_t nodenum = static_cast(octree_nodes.size()); + octree_nodes.push_back({ + .division_point = division_point, + .children = children + }); + return nodenum; + }; + + // build the root node + const uint32_t root_node = build_octree(qvec3i{0,0,0}, grid_size, 0); + + // stats + int stored_cells = 0; + for (auto &leaf : octree_leafs) { + stored_cells += leaf.size[0] * leaf.size[1] * leaf.size[2]; + } + logging::print("octree stored {} grid nodes + {} occluded = {} total, full stored {} (octree is {} percent)\n", + stored_cells, + occluded_cells, + stored_cells + occluded_cells, + occlusion.size(), + 100.0f * stored_cells / (float) occlusion.size()); + + logging::print("octree nodes size: {} bytes ({} * {})\n", + octree_nodes.size() * sizeof(octree_node), + octree_nodes.size(), + sizeof(octree_node)); + + logging::print("octree leafs {} overhead {} bytes\n", + octree_leafs.size(), + octree_leafs.size() * sizeof(octree_leaf)); + + // lookup function + std::function(uint32_t, qvec3i)> octree_lookup_r; + octree_lookup_r = [&](uint32_t node_index, qvec3i test_point) -> std::tuple { + if (node_index & FLAG_OCCLUDED) { + return {{}, true}; + } + if (node_index & FLAG_LEAF) { + auto &leaf = octree_leafs.at(node_index & (~FLAG_LEAF)); + // in actuality, we'd pull the data from a 3D grid stored in the leaf. + int i = get_grid_index(test_point[0], test_point[1], test_point[2]); + return {grid_result[i], occlusion[i]}; + } + auto &node = octree_nodes[node_index]; + int i = child_index(node.division_point, test_point); // [0..7] + return octree_lookup_r(node.children[i], test_point); + }; + + // self-check + for (int z = 0; z < grid_size[2]; ++z) { + for (int y = 0; y < grid_size[1]; ++y) { + for (int x = 0; x < grid_size[0]; ++x) { + auto [color, occluded] = octree_lookup_r(root_node, {x, y, z}); + + int sample_index = get_grid_index(x, y, z); + + // compare against original data + if (occluded) { + Q_assert(occlusion[sample_index]); + } else { + Q_assert(!occlusion[sample_index]); + Q_assert(grid_result[sample_index] == color); + } + } + } + } + + // write out the binary data + const qvec3f grid_dist = qvec3f{light_options.lightgrid_dist.value()}; + + std::ostringstream str(std::ios_base::out | std::ios_base::binary); + str << endianness; + str <= grid_dist; + str <= grid_size; + str <= grid_mins; + str <= num_styles; + + str << static_cast(root_node); + + // the nodes (fixed-size) + str << static_cast(octree_nodes.size()); + for (const auto &node : octree_nodes) { + str << node.division_point; + for (const auto child : node.children) { + str << child; + } + } + + // the leafs (each is variable sized) + str << static_cast(octree_leafs.size()); + for (const auto &leaf : octree_leafs) { + str <= leaf.mins; + str <= leaf.size; + + // logging::print("cluster {} bounds grid mins {} grid size {}\n", cluster, cluster_min_grid_coord, cluster_grid_size); + + auto &cm = leaf.mins; + auto &cs = leaf.size; + + for (int z = cm[2]; z < (cm[2] + cs[2]); ++z) { + for (int y = cm[1]; y < (cm[1] + cs[1]); ++y) { + for (int x = cm[0]; x < (cm[0] + cs[0]); ++x) { + int sample_index = get_grid_index(x, y, z); + + if (occlusion[sample_index]) { + str <= static_cast(0xff); + continue; + } + + const lightgrid_samples_t &samples = grid_result[sample_index]; + str <= static_cast(samples.used_styles()); + for (int i = 0; i < samples.used_styles(); ++i) { + str <= static_cast(samples.samples_by_style[i].style); + str <= samples.samples_by_style[i].round_to_int(); + } + } + } + } + } + + auto vec = StringToVector(str.str()); + logging::print(" {:8} bytes LIGHTGRID_OCTREE\n", vec.size()); + + bspdata->bspx.transfer("LIGHTGRID_OCTREE", std::move(vec)); + } } static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_t *bsp)