io/nanovdb.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file nanovdb.cpp | ||
| 3 | /// @brief NanoVDB file reading, grid headers and dense grid building. | ||
| 4 | #include "ernie/io/nanovdb.h" | ||
| 5 | |||
| 6 | #include "ernie/util/bytes.h" | ||
| 7 | #include "ernie/util/file.h" | ||
| 8 | |||
| 9 | #define PNANOVDB_C | ||
| 10 | #include <PNanoVDB.h> | ||
| 11 | |||
| 12 | #include <cmath> | ||
| 13 | #include <cstring> | ||
| 14 | #include <fstream> | ||
| 15 | #include <iterator> | ||
| 16 | #include <limits> | ||
| 17 | #include <map> | ||
| 18 | #include <stdexcept> | ||
| 19 | #include <tuple> | ||
| 20 | |||
| 21 | namespace ernie { | ||
| 22 | |||
| 23 | namespace { | ||
| 24 | |||
| 25 | constexpr std::uint64_t kMagicNumber = 0x304244566f6e614eULL; // NanoVDB0 | ||
| 26 | constexpr std::uint64_t kMagicGrid = 0x314244566f6e614eULL; // NanoVDB1 | ||
| 27 | constexpr std::uint64_t kMagicFile = 0x324244566f6e614eULL; // NanoVDB2 | ||
| 28 | constexpr std::uint32_t kMajorVersion = 32; | ||
| 29 | // sizeof(FileHeader) and sizeof(FileMetaData), with the offsets | ||
| 30 | // read from the metadata (nanovdb/NanoVDB.h). | ||
| 31 | constexpr std::size_t kFileHeaderSize = 16; | ||
| 32 | constexpr std::size_t kMetaDataSize = 176; | ||
| 33 | constexpr std::size_t kMetaGridSize = 0; | ||
| 34 | constexpr std::size_t kMetaFileSize = 8; | ||
| 35 | constexpr std::size_t kMetaNameSize = 136; | ||
| 36 | |||
| 37 | 47 | std::uint32_t MajorVersion(std::uint32_t version) { | |
| 38 | 47 | return version >> 21; | |
| 39 | } | ||
| 40 | |||
| 41 | // An IEEE 754 half's bits (PNanoVDB's C reader returns them raw). | ||
| 42 | ✗ | double HalfToFloat(short bits) { | |
| 43 | ✗ | const auto h = static_cast<std::uint16_t>(bits); | |
| 44 | ✗ | const int exponent = (h >> 10) & 0x1f; | |
| 45 | ✗ | const int mantissa = h & 0x3ff; | |
| 46 | ✗ | double value = 0.0; | |
| 47 | ✗ | if (exponent == 0) { | |
| 48 | ✗ | value = std::ldexp(mantissa, -24); | |
| 49 | ✗ | } else if (exponent == 31) { | |
| 50 | ✗ | value = mantissa == 0 ? std::numeric_limits<double>::infinity() | |
| 51 | : std::numeric_limits<double>::quiet_NaN(); | ||
| 52 | } else { | ||
| 53 | ✗ | value = std::ldexp(mantissa + 1024, exponent - 25); | |
| 54 | } | ||
| 55 | ✗ | return (h & 0x8000) != 0 ? -value : value; | |
| 56 | } | ||
| 57 | |||
| 58 | 36 | void CheckVersion(std::uint32_t version) { | |
| 59 | 36 | if (MajorVersion(version) != kMajorVersion) { | |
| 60 | ✗ | throw std::runtime_error("NanoVDB major version " + std::to_string(MajorVersion(version)) + | |
| 61 | ✗ | " is not supported (Ernie reads " + std::to_string(kMajorVersion) + | |
| 62 | ✗ | ")"); | |
| 63 | } | ||
| 64 | 36 | } | |
| 65 | |||
| 66 | // The header fields Ernie keeps of the grid `bytes` starts (all of | ||
| 67 | // it, or its head), read through PNanoVDB's own accessors from a copy | ||
| 68 | // aligned for them. | ||
| 69 | 18 | NanoVdbGrid ParseGridHeader(std::span<const std::uint8_t> bytes) { | |
| 70 | 18 | NanoVdbGrid grid; | |
| 71 | // PNanoVDB reads 64-bit words, so read from an aligned copy. | ||
| 72 | 19 | std::vector<std::uint64_t> words((bytes.size() + 7) / 8, 0); | |
| 73 | 18 | std::memcpy(words.data(), bytes.data(), bytes.size()); | |
| 74 | 18 | const pnanovdb_buf_t buf = | |
| 75 | 18 | pnanovdb_make_buf(reinterpret_cast<std::uint32_t*>(words.data()), words.size() * 2); | |
| 76 | 18 | const pnanovdb_grid_handle_t handle{pnanovdb_address_null()}; | |
| 77 | // What PNanoVDB reads at `address`, `size` bytes, must be there. | ||
| 78 | 57 | const auto within = [&](pnanovdb_address_t address, std::size_t size) { | |
| 79 | 2 | return address.byte_offset + size <= bytes.size(); | |
| 80 | 18 | }; | |
| 81 | 18 | grid.size = ReadLittleEndian<std::uint64_t>(bytes, PNANOVDB_GRID_OFF_GRID_SIZE); | |
| 82 | |||
| 83 | 18 | char name[257] = {}; | |
| 84 | 1170 | for (std::uint32_t i = 0; i < 64; ++i) { | |
| 85 | 1152 | const std::uint32_t word = pnanovdb_grid_get_grid_name(buf, handle, i); | |
| 86 | 1152 | std::memcpy(name + 4 * i, &word, 4); | |
| 87 | } | ||
| 88 | 18 | grid.name = name; | |
| 89 | 18 | grid.type = static_cast<NanoVdbType>(pnanovdb_grid_get_grid_type(buf, handle)); | |
| 90 | |||
| 91 | 18 | const pnanovdb_map_handle_t map = pnanovdb_grid_get_map(buf, handle); | |
| 92 | 72 | for (int row = 0; row < 3; ++row) { | |
| 93 | 216 | for (int column = 0; column < 3; ++column) { | |
| 94 | 162 | grid.indexToGrid[column][row] = | |
| 95 | 162 | pnanovdb_map_get_matd(buf, map, static_cast<std::uint32_t>(3 * row + column)); | |
| 96 | } | ||
| 97 | 54 | grid.indexToGrid[3][row] = pnanovdb_map_get_vecd(buf, map, static_cast<std::uint32_t>(row)); | |
| 98 | } | ||
| 99 | 72 | for (std::uint32_t i = 0; i < 3; ++i) { | |
| 100 | 54 | grid.voxelSize[static_cast<int>(i)] = pnanovdb_grid_get_voxel_size(buf, handle, i); | |
| 101 | } | ||
| 102 | |||
| 103 | 18 | const pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buf, handle); | |
| 104 | // The tree holds the root's offset. | ||
| 105 | 18 | if (!within(tree.address, PNANOVDB_TREE_SIZE)) { | |
| 106 | 1 | throw std::runtime_error("NanoVDB grid's tree lies past the bytes read"); | |
| 107 | } | ||
| 108 | 17 | const pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buf, tree); | |
| 109 | 17 | if (!within(root.address, PNANOVDB_ROOT_BASE_SIZE)) { | |
| 110 | ✗ | throw std::runtime_error("NanoVDB grid's root lies past the bytes read"); | |
| 111 | } | ||
| 112 | 17 | const pnanovdb_coord_t lo = pnanovdb_root_get_bbox_min(buf, root); | |
| 113 | 17 | const pnanovdb_coord_t hi = pnanovdb_root_get_bbox_max(buf, root); | |
| 114 | 17 | grid.indexMin = {lo.x, lo.y, lo.z}; | |
| 115 | 17 | grid.indexMax = {hi.x, hi.y, hi.z}; | |
| 116 | 17 | grid.hasStatistics = | |
| 117 | 17 | (pnanovdb_grid_get_flags(buf, handle) & PNANOVDB_GRID_FLAGS_HAS_MIN_MAX) != 0; | |
| 118 | 17 | if (grid.Empty() || !grid.hasStatistics) { | |
| 119 | return grid; | ||
| 120 | } | ||
| 121 | |||
| 122 | 2 | const pnanovdb_grid_type_t type = pnanovdb_grid_get_grid_type(buf, handle); | |
| 123 | 2 | const pnanovdb_address_t min = pnanovdb_root_get_min_address(type, buf, root); | |
| 124 | 2 | const pnanovdb_address_t max = pnanovdb_root_get_max_address(type, buf, root); | |
| 125 | 2 | if (!within(min, 12) || !within(max, 12)) { | |
| 126 | ✗ | grid.hasStatistics = false; | |
| 127 | ✗ | return grid; | |
| 128 | } | ||
| 129 | 2 | switch (grid.type) { | |
| 130 | 2 | case NanoVdbType::Float: | |
| 131 | 2 | case NanoVdbType::Fp4: | |
| 132 | 2 | case NanoVdbType::Fp8: | |
| 133 | 2 | case NanoVdbType::Fp16: | |
| 134 | 2 | case NanoVdbType::FpN: | |
| 135 | 2 | grid.minValue = pnanovdb_read_float(buf, min); | |
| 136 | 2 | grid.maxValue = pnanovdb_read_float(buf, max); | |
| 137 | 2 | break; | |
| 138 | ✗ | case NanoVdbType::Double: | |
| 139 | ✗ | grid.minValue = pnanovdb_read_double(buf, min); | |
| 140 | ✗ | grid.maxValue = pnanovdb_read_double(buf, max); | |
| 141 | ✗ | break; | |
| 142 | ✗ | case NanoVdbType::Half: | |
| 143 | ✗ | grid.minValue = HalfToFloat(pnanovdb_read_half(buf, min)); | |
| 144 | ✗ | grid.maxValue = HalfToFloat(pnanovdb_read_half(buf, max)); | |
| 145 | ✗ | break; | |
| 146 | ✗ | case NanoVdbType::Vec3f: { | |
| 147 | // NanoVDB orders vectors by length. | ||
| 148 | ✗ | const pnanovdb_vec3_t a = pnanovdb_read_vec3(buf, min); | |
| 149 | ✗ | const pnanovdb_vec3_t b = pnanovdb_read_vec3(buf, max); | |
| 150 | ✗ | grid.minValue = std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); | |
| 151 | ✗ | grid.maxValue = std::sqrt(b.x * b.x + b.y * b.y + b.z * b.z); | |
| 152 | ✗ | break; | |
| 153 | } | ||
| 154 | default: | ||
| 155 | break; | ||
| 156 | } | ||
| 157 | return grid; | ||
| 158 | 18 | } | |
| 159 | |||
| 160 | // The grid `bytes` holds, with its own buffer. | ||
| 161 | 9 | NanoVdbGrid ParseGrid(std::span<const std::uint8_t> bytes) { | |
| 162 | 9 | NanoVdbGrid grid = ParseGridHeader(bytes); | |
| 163 | // Each grid of a multi-grid buffer becomes its own buffer. | ||
| 164 | 9 | std::vector<std::uint64_t> words((bytes.size() + 7) / 8, 0); | |
| 165 | 9 | std::memcpy(words.data(), bytes.data(), bytes.size()); | |
| 166 | 9 | const pnanovdb_buf_t buf = | |
| 167 | 9 | pnanovdb_make_buf(reinterpret_cast<std::uint32_t*>(words.data()), words.size() * 2); | |
| 168 | 9 | const pnanovdb_grid_handle_t handle{pnanovdb_address_null()}; | |
| 169 | 9 | pnanovdb_grid_set_grid_index(buf, handle, 0); | |
| 170 | 9 | pnanovdb_grid_set_grid_count(buf, handle, 1); | |
| 171 | 9 | grid.buffer.resize(words.size() * 8); | |
| 172 | 9 | std::memcpy(grid.buffer.data(), words.data(), grid.buffer.size()); | |
| 173 | 9 | return grid; | |
| 174 | 9 | } | |
| 175 | |||
| 176 | // The grids of a raw buffer, or only their headers: each GridData header gives its grid's size. | ||
| 177 | 16 | std::vector<NanoVdbGrid> RawGrids(std::span<const std::uint8_t> bytes, bool headsOnly = false) { | |
| 178 | 16 | std::vector<NanoVdbGrid> grids; | |
| 179 | 16 | std::size_t offset = 0; | |
| 180 | 32 | while (offset + PNANOVDB_GRID_SIZE <= bytes.size()) { | |
| 181 | 16 | const auto magic = ReadLittleEndian<std::uint64_t>(bytes, offset); | |
| 182 | 16 | if (magic != kMagicNumber && magic != kMagicGrid) { | |
| 183 | ✗ | throw std::runtime_error("NanoVDB grid signature not found"); | |
| 184 | } | ||
| 185 | 16 | CheckVersion(ReadLittleEndian<std::uint32_t>(bytes, offset + PNANOVDB_GRID_OFF_VERSION)); | |
| 186 | 16 | const auto size = | |
| 187 | 16 | ReadLittleEndian<std::uint64_t>(bytes, offset + PNANOVDB_GRID_OFF_GRID_SIZE); | |
| 188 | 16 | if (size < PNANOVDB_GRID_SIZE || offset + size > bytes.size()) { | |
| 189 | ✗ | throw std::runtime_error("NanoVDB grid size runs past its data"); | |
| 190 | } | ||
| 191 | 16 | const std::span<const std::uint8_t> grid = | |
| 192 | 16 | bytes.subspan(offset, static_cast<std::size_t>(size)); | |
| 193 | 39 | grids.push_back(headsOnly ? NanoVdbGridHead(grid.first(std::min( | |
| 194 | 7 | static_cast<std::size_t>(size), kNanoVdbHeadBytes))) | |
| 195 | : ParseGrid(grid)); | ||
| 196 | 16 | offset += size; | |
| 197 | } | ||
| 198 | 16 | if (grids.empty()) { | |
| 199 | ✗ | throw std::runtime_error("NanoVDB data holds no grid"); | |
| 200 | } | ||
| 201 | 16 | return grids; | |
| 202 | ✗ | } | |
| 203 | |||
| 204 | } // namespace | ||
| 205 | |||
| 206 | 28 | bool NanoVdbGrid::Empty() const { | |
| 207 | 28 | return glm::any(glm::greaterThan(indexMin, indexMax)); | |
| 208 | } | ||
| 209 | |||
| 210 | 1 | bool NanoVdbGrid::Scalar() const { | |
| 211 | 1 | switch (type) { | |
| 212 | case NanoVdbType::Float: | ||
| 213 | case NanoVdbType::Half: | ||
| 214 | case NanoVdbType::Fp4: | ||
| 215 | case NanoVdbType::Fp8: | ||
| 216 | case NanoVdbType::Fp16: | ||
| 217 | case NanoVdbType::FpN: | ||
| 218 | return true; | ||
| 219 | ✗ | default: | |
| 220 | ✗ | return false; | |
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | 1 | bool NanoVdbGrid::Vector() const { | |
| 225 | 1 | return type == NanoVdbType::Vec3f; | |
| 226 | } | ||
| 227 | |||
| 228 | 9 | Box NanoVdbGrid::GridBounds() const { | |
| 229 | 9 | if (Empty()) { | |
| 230 | ✗ | const glm::dvec3 origin(indexToGrid[3]); | |
| 231 | ✗ | return {origin, origin}; | |
| 232 | } | ||
| 233 | 9 | return Box{glm::dvec3(indexMin) - 1.0, glm::dvec3(indexMax) + 1.0}.Transformed(indexToGrid); | |
| 234 | } | ||
| 235 | |||
| 236 | 4 | std::vector<NanoVdbGrid> ParseNanoVdbGrids(std::span<const std::uint8_t> bytes) { | |
| 237 | 4 | return RawGrids(bytes); | |
| 238 | } | ||
| 239 | |||
| 240 | 1 | std::vector<NanoVdbGrid> ParseNanoVdbHeads(std::span<const std::uint8_t> bytes) { | |
| 241 | 1 | return RawGrids(bytes, true); | |
| 242 | } | ||
| 243 | |||
| 244 | 9 | NanoVdbGrid NanoVdbGridHead(std::span<const std::uint8_t> head) { | |
| 245 | 9 | const auto magic = ReadLittleEndian<std::uint64_t>(head, 0); | |
| 246 | 9 | if (magic != kMagicNumber && magic != kMagicGrid) { | |
| 247 | ✗ | throw std::runtime_error("NanoVDB grid signature not found"); | |
| 248 | } | ||
| 249 | 9 | CheckVersion(ReadLittleEndian<std::uint32_t>(head, PNANOVDB_GRID_OFF_VERSION)); | |
| 250 | 9 | NanoVdbGrid grid = ParseGridHeader(head); | |
| 251 | 8 | if (grid.size < PNANOVDB_GRID_SIZE) { | |
| 252 | ✗ | throw std::runtime_error("NanoVDB grid size is too small"); | |
| 253 | } | ||
| 254 | 8 | return grid; | |
| 255 | ✗ | } | |
| 256 | |||
| 257 | 15 | static std::vector<NanoVdbGrid> ReadGrids(const std::filesystem::path& path, bool headsOnly) { | |
| 258 | // Grids are copied out of the mapping into buffers aligned for PNanoVDB; heads touch only the | ||
| 259 | // pages holding them. | ||
| 260 | 15 | const MappedFile file(path); | |
| 261 | 13 | const std::span<const std::uint8_t> data( | |
| 262 | 13 | reinterpret_cast<const std::uint8_t*>(file.Bytes().data()), file.Bytes().size()); | |
| 263 | 13 | const std::string where = " in " + path.string(); | |
| 264 | 13 | try { | |
| 265 | 13 | const auto magic = ReadLittleEndian<std::uint64_t>(data, 0); | |
| 266 | 13 | if (magic == kMagicGrid) { | |
| 267 | 1 | return RawGrids(data, headsOnly); | |
| 268 | } | ||
| 269 | 12 | if (magic != kMagicNumber && magic != kMagicFile) { | |
| 270 | 1 | throw std::runtime_error("not a NanoVDB file"); | |
| 271 | } | ||
| 272 | // A file of segments; a raw buffer written before NanoVDB | ||
| 273 | // 32.6 also starts with NanoVDB0 but holds the grid version | ||
| 274 | // where a segment's header holds its own. | ||
| 275 | 11 | if (MajorVersion(ReadLittleEndian<std::uint32_t>(data, 8)) != kMajorVersion && | |
| 276 | ✗ | MajorVersion(ReadLittleEndian<std::uint32_t>(data, PNANOVDB_GRID_OFF_VERSION)) == | |
| 277 | kMajorVersion) { | ||
| 278 | ✗ | return RawGrids(data, headsOnly); | |
| 279 | } | ||
| 280 | 11 | std::vector<NanoVdbGrid> grids; | |
| 281 | 11 | std::size_t offset = 0; | |
| 282 | 21 | while (offset < data.size()) { | |
| 283 | 11 | CheckVersion(ReadLittleEndian<std::uint32_t>(data, offset + 8)); | |
| 284 | 11 | const auto count = ReadLittleEndian<std::uint16_t>(data, offset + 12); | |
| 285 | 11 | const auto codec = ReadLittleEndian<std::uint16_t>(data, offset + 14); | |
| 286 | 11 | if (codec != 0) { | |
| 287 | 3 | throw std::runtime_error(std::string("compressed (") + | |
| 288 | 1 | (codec == 1 ? "zip" : "blosc") + | |
| 289 | 3 | "); write it uncompressed"); | |
| 290 | } | ||
| 291 | 10 | offset += kFileHeaderSize; | |
| 292 | 10 | std::vector<std::uint64_t> sizes; | |
| 293 | 20 | for (std::uint16_t i = 0; i < count; ++i) { | |
| 294 | 10 | const auto gridSize = ReadLittleEndian<std::uint64_t>(data, offset + kMetaGridSize); | |
| 295 | 10 | const auto fileSize = ReadLittleEndian<std::uint64_t>(data, offset + kMetaFileSize); | |
| 296 | 10 | if (gridSize != fileSize) { | |
| 297 | ✗ | throw std::runtime_error("grid sizes disagree"); | |
| 298 | } | ||
| 299 | 10 | sizes.push_back(gridSize); | |
| 300 | 20 | offset += | |
| 301 | 10 | kMetaDataSize + ReadLittleEndian<std::uint32_t>(data, offset + kMetaNameSize); | |
| 302 | } | ||
| 303 | 20 | for (const std::uint64_t size : sizes) { | |
| 304 | 10 | if (offset + size > data.size()) { | |
| 305 | ✗ | throw std::runtime_error("grid runs past the file's end"); | |
| 306 | } | ||
| 307 | 10 | std::vector<NanoVdbGrid> one = RawGrids(data.subspan(offset, size), headsOnly); | |
| 308 | 10 | grids.insert(grids.end(), std::make_move_iterator(one.begin()), | |
| 309 | std::make_move_iterator(one.end())); | ||
| 310 | 10 | offset += size; | |
| 311 | 10 | } | |
| 312 | // The next segment, if any, starts with its own magic. | ||
| 313 | 10 | if (offset < data.size()) { | |
| 314 | ✗ | const auto next = ReadLittleEndian<std::uint64_t>(data, offset); | |
| 315 | ✗ | if (next != kMagicNumber && next != kMagicFile) { | |
| 316 | break; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | 10 | } | |
| 320 | 10 | return grids; | |
| 321 | 13 | } catch (const std::runtime_error& error) { | |
| 322 | 4 | throw std::runtime_error("NanoVDB: " + std::string(error.what()) + where); | |
| 323 | 2 | } | |
| 324 | 13 | } | |
| 325 | |||
| 326 | 8 | std::vector<NanoVdbGrid> ReadNanoVdb(const std::filesystem::path& path) { | |
| 327 | 8 | return ReadGrids(path, false); | |
| 328 | } | ||
| 329 | |||
| 330 | 7 | std::vector<NanoVdbGrid> ReadNanoVdbHeads(const std::filesystem::path& path) { | |
| 331 | 7 | return ReadGrids(path, true); | |
| 332 | } | ||
| 333 | |||
| 334 | 12 | const NanoVdbGrid* FindNanoVdbGrid(std::span<const NanoVdbGrid> grids, std::string_view name, | |
| 335 | int index) { | ||
| 336 | // An index into the caller's grids, which cppcheck follows where | ||
| 337 | // a range over the span would look like a temporary to it. | ||
| 338 | 12 | int seen = 0; | |
| 339 | 23 | for (std::size_t i = 0; i < grids.size(); ++i) { | |
| 340 | 21 | if (!name.empty() && grids[i].name != name) { | |
| 341 | 6 | continue; | |
| 342 | } | ||
| 343 | 15 | if (seen++ == index) { | |
| 344 | 10 | return grids.data() + i; | |
| 345 | } | ||
| 346 | } | ||
| 347 | return nullptr; | ||
| 348 | } | ||
| 349 | |||
| 350 | namespace { | ||
| 351 | |||
| 352 | // `DenseNanoVdbGrid`, or with `everyVoxel` `DenseNanoVdbTopology` (no | ||
| 353 | // `values`, every voxel of the shape active), filling `layout` when | ||
| 354 | // given. | ||
| 355 | 5 | std::vector<std::uint8_t> BuildDense(std::span<const float> values, const glm::ivec3& shape, | |
| 356 | double voxelSize, const glm::dvec3& origin, | ||
| 357 | std::string_view name, bool everyVoxel, | ||
| 358 | DenseNanoVdbLayout* layout) { | ||
| 359 | 5 | if (glm::any(glm::lessThan(shape, glm::ivec3(0))) || | |
| 360 | 4 | (!everyVoxel && values.size() != static_cast<std::size_t>(shape.x) * | |
| 361 | 4 | static_cast<std::size_t>(shape.y) * | |
| 362 | 4 | static_cast<std::size_t>(shape.z))) { | |
| 363 | 1 | throw std::invalid_argument("DenseNanoVdbGrid: values do not fill the shape"); | |
| 364 | } | ||
| 365 | 4 | if (!(voxelSize > 0.0)) { | |
| 366 | ✗ | throw std::invalid_argument("DenseNanoVdbGrid: the voxel size must be positive"); | |
| 367 | } | ||
| 368 | 4 | constexpr pnanovdb_grid_type_t kType = PNANOVDB_GRID_TYPE_FLOAT; | |
| 369 | 523 | const auto constant = [](auto member) { return pnanovdb_grid_type_constants[kType].*member; }; | |
| 370 | 4 | using Constants = pnanovdb_grid_type_constants_t; | |
| 371 | 34173 | const auto at = [&](int i, int j, int k) { | |
| 372 | 65938 | const auto index = [](int n) { return static_cast<std::size_t>(n); }; | |
| 373 | 34169 | return values.empty() | |
| 374 | 34169 | ? 0.0f | |
| 375 | 31769 | : values[(index(i) * index(shape.y) + index(j)) * index(shape.z) + index(k)]; | |
| 376 | 4 | }; | |
| 377 | // The leaves (8^3) holding active values, then the lower (128^3) | ||
| 378 | // and upper (4096^3) nodes above them, each keyed by its origin. | ||
| 379 | 353 | const auto nodeOrigin = [](const glm::ivec3& ijk, int size) { | |
| 380 | 349 | return ijk & glm::ivec3(~(size - 1)); | |
| 381 | }; | ||
| 382 | 1794 | const auto less = [](const glm::ivec3& a, const glm::ivec3& b) { | |
| 383 | 1790 | return std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z); | |
| 384 | }; | ||
| 385 | 4 | std::map<glm::ivec3, std::size_t, decltype(less)> leafIndex(less); | |
| 386 | 4 | std::vector<glm::ivec3> leaves; | |
| 387 | 12 | for (int x = 0; x < shape.x; x += 8) { | |
| 388 | 22 | for (int y = 0; y < shape.y; y += 8) { | |
| 389 | 136 | for (int z = 0; z < shape.z; z += 8) { | |
| 390 | 122 | bool active = everyVoxel; | |
| 391 | 335 | for (int i = x; i < std::min(x + 8, shape.x) && !active; ++i) { | |
| 392 | 911 | for (int j = y; j < std::min(y + 8, shape.y) && !active; ++j) { | |
| 393 | 4652 | for (int k = z; k < std::min(z + 8, shape.z); ++k) { | |
| 394 | 4057 | if (at(i, j, k) != 0.0f) { | |
| 395 | active = true; | ||
| 396 | break; | ||
| 397 | } | ||
| 398 | } | ||
| 399 | } | ||
| 400 | } | ||
| 401 | 122 | if (active) { | |
| 402 | 115 | leafIndex.emplace(glm::ivec3(x, y, z), leaves.size()); | |
| 403 | 115 | leaves.emplace_back(x, y, z); | |
| 404 | } | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | 4 | std::map<glm::ivec3, std::size_t, decltype(less)> lowerIndex(less); | |
| 409 | 4 | std::vector<glm::ivec3> lowers; | |
| 410 | 4 | std::map<glm::ivec3, std::size_t, decltype(less)> upperIndex(less); | |
| 411 | 4 | std::vector<glm::ivec3> uppers; | |
| 412 | 119 | for (const glm::ivec3& leaf : leaves) { | |
| 413 | 115 | const glm::ivec3 lower = nodeOrigin(leaf, 128); | |
| 414 | 115 | if (lowerIndex.emplace(lower, lowers.size()).second) { | |
| 415 | 4 | lowers.push_back(lower); | |
| 416 | } | ||
| 417 | 115 | const glm::ivec3 upper = nodeOrigin(leaf, 4096); | |
| 418 | 115 | if (upperIndex.emplace(upper, uppers.size()).second) { | |
| 419 | 3 | uppers.push_back(upper); | |
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 | // The layout: grid, tree, root and its tiles, then the nodes by | ||
| 424 | // level, uppers first, each 32-byte aligned. | ||
| 425 | 4 | const std::uint64_t tree = PNANOVDB_GRID_SIZE; | |
| 426 | 4 | const std::uint64_t root = tree + PNANOVDB_TREE_SIZE; | |
| 427 | 4 | const std::uint64_t firstUpper = root + constant(&Constants::root_size) + | |
| 428 | 4 | uppers.size() * constant(&Constants::root_tile_size); | |
| 429 | 4 | const std::uint64_t firstLower = firstUpper + uppers.size() * constant(&Constants::upper_size); | |
| 430 | 4 | const std::uint64_t firstLeaf = firstLower + lowers.size() * constant(&Constants::lower_size); | |
| 431 | 4 | const std::uint64_t size = firstLeaf + leaves.size() * constant(&Constants::leaf_size); | |
| 432 | 4 | std::vector<std::uint64_t> words(size / 8, 0); | |
| 433 | 4 | const pnanovdb_buf_t buf = | |
| 434 | 4 | pnanovdb_make_buf(reinterpret_cast<std::uint32_t*>(words.data()), words.size() * 2); | |
| 435 | 248 | const auto address = [](std::uint64_t offset) { | |
| 436 | 244 | return pnanovdb_address_t{static_cast<std::uint32_t>(offset)}; | |
| 437 | }; | ||
| 438 | 261 | const auto coord = [](const glm::ivec3& c) { return pnanovdb_coord_t{c.x, c.y, c.z}; }; | |
| 439 | |||
| 440 | // Each node's active bounds and min and max, filled leaves up. | ||
| 441 | 7 | struct Stats { | |
| 442 | glm::ivec3 low{std::numeric_limits<int>::max()}; | ||
| 443 | glm::ivec3 high{std::numeric_limits<int>::min()}; | ||
| 444 | float min = std::numeric_limits<float>::max(); | ||
| 445 | float max = std::numeric_limits<float>::lowest(); | ||
| 446 | 122 | void Add(const Stats& other) { | |
| 447 | 122 | low = glm::min(low, other.low); | |
| 448 | 122 | high = glm::max(high, other.high); | |
| 449 | 122 | min = std::min(min, other.min); | |
| 450 | 122 | max = std::max(max, other.max); | |
| 451 | 122 | } | |
| 452 | }; | ||
| 453 | 4 | std::vector<Stats> lowerStats(lowers.size()); | |
| 454 | 4 | std::vector<Stats> upperStats(uppers.size()); | |
| 455 | 4 | Stats rootStats; | |
| 456 | 4 | std::uint64_t voxels = 0; | |
| 457 | 119 | for (std::size_t n = 0; n < leaves.size(); ++n) { | |
| 458 | 115 | const glm::ivec3& origin8 = leaves[n]; | |
| 459 | 115 | const pnanovdb_leaf_handle_t leaf{address(firstLeaf + n * constant(&Constants::leaf_size))}; | |
| 460 | 115 | Stats stats; | |
| 461 | 1035 | for (int i = 0; i < 8; ++i) { | |
| 462 | 8280 | for (int j = 0; j < 8; ++j) { | |
| 463 | 66240 | for (int k = 0; k < 8; ++k) { | |
| 464 | 58880 | const glm::ivec3 ijk = origin8 + glm::ivec3(i, j, k); | |
| 465 | 58880 | if (glm::any(glm::greaterThanEqual(ijk, shape))) { | |
| 466 | 39584 | continue; | |
| 467 | } | ||
| 468 | 30112 | const float value = at(ijk.x, ijk.y, ijk.z); | |
| 469 | 30112 | if (value == 0.0f && !everyVoxel) { | |
| 470 | 10816 | continue; | |
| 471 | } | ||
| 472 | 19296 | const auto bit = static_cast<std::uint32_t>((i << 6) | (j << 3) | k); | |
| 473 | 19296 | pnanovdb_leaf_set_value_mask(buf, leaf, bit, 1); | |
| 474 | 19296 | pnanovdb_write_float( | |
| 475 | buf, pnanovdb_leaf_get_table_address(kType, buf, leaf, bit), value); | ||
| 476 | 19296 | stats.low = glm::min(stats.low, ijk); | |
| 477 | 19296 | stats.high = glm::max(stats.high, ijk); | |
| 478 | 19296 | stats.min = std::min(stats.min, value); | |
| 479 | 19296 | stats.max = std::max(stats.max, value); | |
| 480 | 19296 | ++voxels; | |
| 481 | } | ||
| 482 | } | ||
| 483 | } | ||
| 484 | 115 | const pnanovdb_coord_t low = coord(stats.low); | |
| 485 | 115 | pnanovdb_leaf_set_bbox_min(buf, leaf, &low); | |
| 486 | 115 | const glm::ivec3 dif = stats.high - stats.low; | |
| 487 | 115 | pnanovdb_leaf_set_bbox_dif_and_flags( | |
| 488 | 115 | buf, leaf, static_cast<std::uint32_t>(dif.x | (dif.y << 8) | (dif.z << 16))); | |
| 489 | 115 | pnanovdb_write_float( | |
| 490 | buf, pnanovdb_address_offset(leaf.address, constant(&Constants::leaf_off_min)), | ||
| 491 | stats.min); | ||
| 492 | 115 | pnanovdb_write_float( | |
| 493 | buf, pnanovdb_address_offset(leaf.address, constant(&Constants::leaf_off_max)), | ||
| 494 | stats.max); | ||
| 495 | // Its lower node's child. | ||
| 496 | 115 | const std::size_t l = lowerIndex.at(nodeOrigin(origin8, 128)); | |
| 497 | 115 | const pnanovdb_lower_handle_t lower{ | |
| 498 | 115 | address(firstLower + l * constant(&Constants::lower_size))}; | |
| 499 | 115 | const pnanovdb_coord_t at8 = coord(origin8); | |
| 500 | 115 | const std::uint32_t slot = pnanovdb_lower_coord_to_offset(&at8); | |
| 501 | 115 | pnanovdb_lower_set_child_mask(buf, lower, slot, 1); | |
| 502 | 115 | pnanovdb_lower_set_child(kType, buf, lower, slot, leaf); | |
| 503 | 115 | lowerStats[l].Add(stats); | |
| 504 | } | ||
| 505 | 8 | for (std::size_t l = 0; l < lowers.size(); ++l) { | |
| 506 | 4 | const pnanovdb_lower_handle_t lower{ | |
| 507 | 4 | address(firstLower + l * constant(&Constants::lower_size))}; | |
| 508 | 4 | const Stats& stats = lowerStats[l]; | |
| 509 | 4 | const pnanovdb_coord_t low = coord(stats.low); | |
| 510 | 4 | const pnanovdb_coord_t high = coord(stats.high); | |
| 511 | 4 | pnanovdb_lower_set_bbox_min(buf, lower, &low); | |
| 512 | 4 | pnanovdb_lower_set_bbox_max(buf, lower, &high); | |
| 513 | 4 | pnanovdb_write_float( | |
| 514 | buf, pnanovdb_address_offset(lower.address, constant(&Constants::lower_off_min)), | ||
| 515 | 4 | stats.min); | |
| 516 | 4 | pnanovdb_write_float( | |
| 517 | buf, pnanovdb_address_offset(lower.address, constant(&Constants::lower_off_max)), | ||
| 518 | 4 | stats.max); | |
| 519 | 4 | const std::size_t u = upperIndex.at(nodeOrigin(lowers[l], 4096)); | |
| 520 | 4 | const pnanovdb_upper_handle_t upper{ | |
| 521 | 4 | address(firstUpper + u * constant(&Constants::upper_size))}; | |
| 522 | 4 | const pnanovdb_coord_t at128 = coord(lowers[l]); | |
| 523 | 4 | const std::uint32_t slot = pnanovdb_upper_coord_to_offset(&at128); | |
| 524 | 4 | pnanovdb_upper_set_child_mask(buf, upper, slot, 1); | |
| 525 | 4 | pnanovdb_upper_set_child(kType, buf, upper, slot, lower); | |
| 526 | 4 | upperStats[u].Add(stats); | |
| 527 | } | ||
| 528 | 4 | const pnanovdb_root_handle_t rootHandle{address(root)}; | |
| 529 | 7 | for (std::size_t u = 0; u < uppers.size(); ++u) { | |
| 530 | 3 | const pnanovdb_upper_handle_t upper{ | |
| 531 | 3 | address(firstUpper + u * constant(&Constants::upper_size))}; | |
| 532 | 3 | const Stats& stats = upperStats[u]; | |
| 533 | 3 | const pnanovdb_coord_t low = coord(stats.low); | |
| 534 | 3 | const pnanovdb_coord_t high = coord(stats.high); | |
| 535 | 3 | pnanovdb_upper_set_bbox_min(buf, upper, &low); | |
| 536 | 3 | pnanovdb_upper_set_bbox_max(buf, upper, &high); | |
| 537 | 3 | pnanovdb_write_float( | |
| 538 | buf, pnanovdb_address_offset(upper.address, constant(&Constants::upper_off_min)), | ||
| 539 | 3 | stats.min); | |
| 540 | 3 | pnanovdb_write_float( | |
| 541 | buf, pnanovdb_address_offset(upper.address, constant(&Constants::upper_off_max)), | ||
| 542 | 3 | stats.max); | |
| 543 | 3 | const pnanovdb_root_tile_handle_t tile{address(root + constant(&Constants::root_size) + | |
| 544 | 3 | u * constant(&Constants::root_tile_size))}; | |
| 545 | 3 | const pnanovdb_coord_t at4096 = coord(uppers[u]); | |
| 546 | 3 | pnanovdb_root_tile_set_key(buf, tile, pnanovdb_coord_to_key(&at4096)); | |
| 547 | 3 | pnanovdb_root_set_child(kType, buf, rootHandle, tile, upper); | |
| 548 | 3 | rootStats.Add(stats); | |
| 549 | } | ||
| 550 | |||
| 551 | // The root, tree and grid headers. | ||
| 552 | 4 | if (!leaves.empty()) { | |
| 553 | 3 | const pnanovdb_coord_t low = coord(rootStats.low); | |
| 554 | 3 | const pnanovdb_coord_t high = coord(rootStats.high); | |
| 555 | 3 | pnanovdb_root_set_bbox_min(buf, rootHandle, &low); | |
| 556 | 3 | pnanovdb_root_set_bbox_max(buf, rootHandle, &high); | |
| 557 | 3 | pnanovdb_write_float( | |
| 558 | buf, pnanovdb_address_offset(rootHandle.address, constant(&Constants::root_off_min)), | ||
| 559 | rootStats.min); | ||
| 560 | 3 | pnanovdb_write_float( | |
| 561 | buf, pnanovdb_address_offset(rootHandle.address, constant(&Constants::root_off_max)), | ||
| 562 | rootStats.max); | ||
| 563 | } else { | ||
| 564 | // No active values: an empty box, min past max. | ||
| 565 | 1 | const pnanovdb_coord_t low{1, 1, 1}; | |
| 566 | 1 | const pnanovdb_coord_t high{0, 0, 0}; | |
| 567 | 1 | pnanovdb_root_set_bbox_min(buf, rootHandle, &low); | |
| 568 | 1 | pnanovdb_root_set_bbox_max(buf, rootHandle, &high); | |
| 569 | } | ||
| 570 | 4 | pnanovdb_root_set_tile_count(buf, rootHandle, static_cast<std::uint32_t>(uppers.size())); | |
| 571 | |||
| 572 | 4 | const pnanovdb_tree_handle_t treeHandle{address(tree)}; | |
| 573 | 4 | pnanovdb_tree_set_node_offset_leaf(buf, treeHandle, leaves.empty() ? 0 : firstLeaf - tree); | |
| 574 | 4 | pnanovdb_tree_set_node_offset_lower(buf, treeHandle, lowers.empty() ? 0 : firstLower - tree); | |
| 575 | 4 | pnanovdb_tree_set_node_offset_upper(buf, treeHandle, uppers.empty() ? 0 : firstUpper - tree); | |
| 576 | 4 | pnanovdb_tree_set_node_offset_root(buf, treeHandle, root - tree); | |
| 577 | 4 | pnanovdb_tree_set_node_count_leaf(buf, treeHandle, static_cast<std::uint32_t>(leaves.size())); | |
| 578 | 4 | pnanovdb_tree_set_node_count_lower(buf, treeHandle, static_cast<std::uint32_t>(lowers.size())); | |
| 579 | 4 | pnanovdb_tree_set_node_count_upper(buf, treeHandle, static_cast<std::uint32_t>(uppers.size())); | |
| 580 | 4 | pnanovdb_tree_set_voxel_count(buf, treeHandle, voxels); | |
| 581 | |||
| 582 | 4 | const pnanovdb_grid_handle_t grid{address(0)}; | |
| 583 | 4 | pnanovdb_grid_set_magic(buf, grid, PNANOVDB_MAGIC_GRID); | |
| 584 | 4 | pnanovdb_grid_set_version(buf, grid, | |
| 585 | pnanovdb_make_version(PNANOVDB_MAJOR_VERSION_NUMBER, | ||
| 586 | PNANOVDB_MINOR_VERSION_NUMBER, | ||
| 587 | PNANOVDB_PATCH_VERSION_NUMBER)); | ||
| 588 | // A topology's values come later: no min and max for them. | ||
| 589 | 8 | pnanovdb_grid_set_flags(buf, grid, | |
| 590 | PNANOVDB_GRID_FLAGS_HAS_BBOX | PNANOVDB_GRID_FLAGS_IS_BREADTH_FIRST | | ||
| 591 | 4 | (values.empty() ? 0u : PNANOVDB_GRID_FLAGS_HAS_MIN_MAX)); | |
| 592 | 4 | pnanovdb_grid_set_grid_index(buf, grid, 0); | |
| 593 | 4 | pnanovdb_grid_set_grid_count(buf, grid, 1); | |
| 594 | 4 | pnanovdb_grid_set_grid_size(buf, grid, size); | |
| 595 | 4 | char gridName[256] = {}; | |
| 596 | 4 | std::memcpy(gridName, name.data(), std::min<std::size_t>(name.size(), 255)); | |
| 597 | 260 | for (std::uint32_t i = 0; i < 64; ++i) { | |
| 598 | 256 | std::uint32_t word = 0; | |
| 599 | 256 | std::memcpy(&word, gridName + 4 * i, 4); | |
| 600 | 256 | pnanovdb_grid_set_grid_name(buf, grid, i, word); | |
| 601 | } | ||
| 602 | 4 | const pnanovdb_map_handle_t map{address(PNANOVDB_GRID_OFF_MAP)}; | |
| 603 | 16 | for (std::uint32_t row = 0; row < 3; ++row) { | |
| 604 | 48 | for (std::uint32_t column = 0; column < 3; ++column) { | |
| 605 | 36 | const double scale = row == column ? voxelSize : 0.0; | |
| 606 | 36 | const double inverse = row == column ? 1.0 / voxelSize : 0.0; | |
| 607 | 36 | pnanovdb_map_set_matf(buf, map, 3 * row + column, static_cast<float>(scale)); | |
| 608 | 36 | pnanovdb_map_set_invmatf(buf, map, 3 * row + column, static_cast<float>(inverse)); | |
| 609 | 36 | pnanovdb_map_set_matd(buf, map, 3 * row + column, scale); | |
| 610 | 36 | pnanovdb_map_set_invmatd(buf, map, 3 * row + column, inverse); | |
| 611 | } | ||
| 612 | 12 | const double offset = origin[static_cast<glm::length_t>(row)]; | |
| 613 | 12 | pnanovdb_map_set_vecf(buf, map, row, static_cast<float>(offset)); | |
| 614 | 12 | pnanovdb_map_set_vecd(buf, map, row, offset); | |
| 615 | } | ||
| 616 | 4 | pnanovdb_map_set_taperf(buf, map, 0, 1.0f); | |
| 617 | 4 | pnanovdb_map_set_taperd(buf, map, 0, 1.0); | |
| 618 | 4 | if (!leaves.empty()) { | |
| 619 | 12 | for (int a = 0; a < 3; ++a) { | |
| 620 | 27 | pnanovdb_grid_set_world_bbox(buf, grid, static_cast<std::uint32_t>(a), | |
| 621 | 9 | origin[a] + voxelSize * rootStats.low[a]); | |
| 622 | 27 | pnanovdb_grid_set_world_bbox(buf, grid, static_cast<std::uint32_t>(a + 3), | |
| 623 | 9 | origin[a] + voxelSize * (rootStats.high[a] + 1)); | |
| 624 | } | ||
| 625 | } | ||
| 626 | 16 | for (std::uint32_t a = 0; a < 3; ++a) { | |
| 627 | 12 | pnanovdb_grid_set_voxel_size(buf, grid, a, voxelSize); | |
| 628 | } | ||
| 629 | 4 | pnanovdb_grid_set_grid_class(buf, grid, PNANOVDB_GRID_CLASS_FOG_VOLUME); | |
| 630 | 4 | pnanovdb_grid_set_grid_type(buf, grid, kType); | |
| 631 | |||
| 632 | 4 | if (layout != nullptr) { | |
| 633 | 1 | layout->firstLeaf = firstLeaf; | |
| 634 | 1 | layout->leafBytes = constant(&Constants::leaf_size); | |
| 635 | 1 | layout->valuesOffset = constant(&Constants::leaf_off_table); | |
| 636 | 1 | layout->leaves = (shape + 7) / 8; | |
| 637 | } | ||
| 638 | 4 | std::vector<std::uint8_t> bytes(size); | |
| 639 | 4 | std::memcpy(bytes.data(), words.data(), size); | |
| 640 | 4 | return bytes; | |
| 641 | 4 | } | |
| 642 | |||
| 643 | } // namespace | ||
| 644 | |||
| 645 | 4 | std::vector<std::uint8_t> DenseNanoVdbGrid(std::span<const float> values, const glm::ivec3& shape, | |
| 646 | double voxelSize, const glm::dvec3& origin, | ||
| 647 | std::string_view name) { | ||
| 648 | 4 | return BuildDense(values, shape, voxelSize, origin, name, false, nullptr); | |
| 649 | } | ||
| 650 | |||
| 651 | 1 | std::vector<std::uint8_t> DenseNanoVdbTopology(const glm::ivec3& shape, double voxelSize, | |
| 652 | const glm::dvec3& origin, std::string_view name, | ||
| 653 | DenseNanoVdbLayout& layout) { | ||
| 654 | 1 | return BuildDense({}, shape, voxelSize, origin, name, true, &layout); | |
| 655 | } | ||
| 656 | |||
| 657 | } // namespace ernie | ||
| 658 |