GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
376 / 425
88.5%
Branches:
246 / 385
63.9%
Functions:
26 / 27
96.3%

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
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (exponent == 0) {
48 ✗ value = std::ldexp(mantissa, -24);
49
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } else if (exponent == 31) {
50
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ 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
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return (h & 0x8000) != 0 ? -value : value;
56 }
57
58 36 void CheckVersion(std::uint32_t version) {
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if (MajorVersion(version) != kMajorVersion) {
60
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("NanoVDB major version " + std::to_string(MajorVersion(version)) +
61
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ " is not supported (Ernie reads " + std::to_string(kMajorVersion) +
62
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ ")");
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
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
19 std::vector<std::uint64_t> words((bytes.size() + 7) / 8, 0);
73
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 std::memcpy(words.data(), bytes.data(), bytes.size());
74
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 const pnanovdb_buf_t buf =
75
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 pnanovdb_make_buf(reinterpret_cast<std::uint32_t*>(words.data()), words.size() * 2);
76
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
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
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 return address.byte_offset + size <= bytes.size();
80 18 };
81
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 grid.size = ReadLittleEndian<std::uint64_t>(bytes, PNANOVDB_GRID_OFF_GRID_SIZE);
82
83 18 char name[257] = {};
84
2/2
✓ Branch 0 taken 1152 times.
✓ Branch 1 taken 18 times.
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
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
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
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
72 for (int row = 0; row < 3; ++row) {
93
2/2
✓ Branch 0 taken 162 times.
✓ Branch 1 taken 54 times.
216 for (int column = 0; column < 3; ++column) {
94 162 grid.indexToGrid[column][row] =
95
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 162 times.
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
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 17 times.
18 const pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buf, handle);
104 // The tree holds the root's offset.
105
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 17 times.
18 if (!within(tree.address, PNANOVDB_TREE_SIZE)) {
106
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::runtime_error("NanoVDB grid's tree lies past the bytes read");
107 }
108
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 const pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buf, tree);
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (!within(root.address, PNANOVDB_ROOT_BASE_SIZE)) {
110
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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
4/4
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 14 times.
17 if (grid.Empty() || !grid.hasStatistics) {
119 return grid;
120 }
121
122
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const pnanovdb_grid_type_t type = pnanovdb_grid_get_grid_type(buf, handle);
123
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const pnanovdb_address_t min = pnanovdb_root_get_min_address(type, buf, root);
124
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const pnanovdb_address_t max = pnanovdb_root_get_max_address(type, buf, root);
125
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (!within(min, 12) || !within(max, 12)) {
126 ✗ grid.hasStatistics = false;
127 ✗ return grid;
128 }
129
1/5
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
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
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
9 std::vector<std::uint64_t> words((bytes.size() + 7) / 8, 0);
165
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 std::memcpy(words.data(), bytes.data(), bytes.size());
166
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 const pnanovdb_buf_t buf =
167
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 pnanovdb_make_buf(reinterpret_cast<std::uint32_t*>(words.data()), words.size() * 2);
168
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 const pnanovdb_grid_handle_t handle{pnanovdb_address_null()};
169
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 pnanovdb_grid_set_grid_index(buf, handle, 0);
170
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 pnanovdb_grid_set_grid_count(buf, handle, 1);
171
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
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
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 while (offset + PNANOVDB_GRID_SIZE <= bytes.size()) {
181
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 const auto magic = ReadLittleEndian<std::uint64_t>(bytes, offset);
182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (magic != kMagicNumber && magic != kMagicGrid) {
183
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("NanoVDB grid signature not found");
184 }
185
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 CheckVersion(ReadLittleEndian<std::uint32_t>(bytes, offset + PNANOVDB_GRID_OFF_VERSION));
186 16 const auto size =
187
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 ReadLittleEndian<std::uint64_t>(bytes, offset + PNANOVDB_GRID_OFF_GRID_SIZE);
188
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
16 if (size < PNANOVDB_GRID_SIZE || offset + size > bytes.size()) {
189
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("NanoVDB grid size runs past its data");
190 }
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 const std::span<const std::uint8_t> grid =
192 16 bytes.subspan(offset, static_cast<std::size_t>(size));
193
4/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
39 grids.push_back(headsOnly ? NanoVdbGridHead(grid.first(std::min(
194
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 static_cast<std::size_t>(size), kNanoVdbHeadBytes)))
195 : ParseGrid(grid));
196 16 offset += size;
197 }
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (grids.empty()) {
199
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (magic != kMagicNumber && magic != kMagicGrid) {
247
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (grid.size < PNANOVDB_GRID_SIZE) {
252
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const std::span<const std::uint8_t> data(
262
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 reinterpret_cast<const std::uint8_t*>(file.Bytes().data()), file.Bytes().size());
263
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 const std::string where = " in " + path.string();
264 13 try {
265
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const auto magic = ReadLittleEndian<std::uint64_t>(data, 0);
266
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 if (magic == kMagicGrid) {
267
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 return RawGrids(data, headsOnly);
268 }
269
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 11 times.
12 if (magic != kMagicNumber && magic != kMagicFile) {
270
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
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
2/4
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
11 if (MajorVersion(ReadLittleEndian<std::uint32_t>(data, 8)) != kMajorVersion &&
276
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ MajorVersion(ReadLittleEndian<std::uint32_t>(data, PNANOVDB_GRID_OFF_VERSION)) ==
277 kMajorVersion) {
278
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ return RawGrids(data, headsOnly);
279 }
280 11 std::vector<NanoVdbGrid> grids;
281 11 std::size_t offset = 0;
282
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 10 times.
21 while (offset < data.size()) {
283
2/4
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
11 CheckVersion(ReadLittleEndian<std::uint32_t>(data, offset + 8));
284
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 const auto count = ReadLittleEndian<std::uint16_t>(data, offset + 12);
285
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 const auto codec = ReadLittleEndian<std::uint16_t>(data, offset + 14);
286
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 10 times.
11 if (codec != 0) {
287
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
3 throw std::runtime_error(std::string("compressed (") +
288 1 (codec == 1 ? "zip" : "blosc") +
289
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
3 "); write it uncompressed");
290 }
291 10 offset += kFileHeaderSize;
292 10 std::vector<std::uint64_t> sizes;
293
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 for (std::uint16_t i = 0; i < count; ++i) {
294
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 const auto gridSize = ReadLittleEndian<std::uint64_t>(data, offset + kMetaGridSize);
295
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 const auto fileSize = ReadLittleEndian<std::uint64_t>(data, offset + kMetaFileSize);
296
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (gridSize != fileSize) {
297
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("grid sizes disagree");
298 }
299
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 sizes.push_back(gridSize);
300 20 offset +=
301
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 kMetaDataSize + ReadLittleEndian<std::uint32_t>(data, offset + kMetaNameSize);
302 }
303
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 for (const std::uint64_t size : sizes) {
304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (offset + size > data.size()) {
305
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("grid runs past the file's end");
306 }
307
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
10 std::vector<NanoVdbGrid> one = RawGrids(data.subspan(offset, size), headsOnly);
308
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (offset < data.size()) {
314
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto next = ReadLittleEndian<std::uint64_t>(data, offset);
315
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (next != kMagicNumber && next != kMagicFile) {
316 break;
317 }
318 }
319 10 }
320 10 return grids;
321
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
13 } catch (const std::runtime_error& error) {
322
4/8
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
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
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 2 times.
23 for (std::size_t i = 0; i < grids.size(); ++i) {
340
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
21 if (!name.empty() && grids[i].name != name) {
341 6 continue;
342 }
343
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
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
3/4
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 time.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 static_cast<std::size_t>(shape.z))) {
363
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::invalid_argument("DenseNanoVdbGrid: values do not fill the shape");
364 }
365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!(voxelSize > 0.0)) {
366
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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
2/2
✓ Branch 0 taken 31769 times.
✓ Branch 1 taken 2400 times.
34169 return values.empty()
374
2/2
✓ Branch 0 taken 31769 times.
✓ Branch 1 taken 2400 times.
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
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (int x = 0; x < shape.x; x += 8) {
388
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 8 times.
22 for (int y = 0; y < shape.y; y += 8) {
389
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 14 times.
136 for (int z = 0; z < shape.z; z += 8) {
390 122 bool active = everyVoxel;
391
4/4
✓ Branch 0 taken 328 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 115 times.
✓ Branch 3 taken 213 times.
335 for (int i = x; i < std::min(x + 8, shape.x) && !active; ++i) {
392
4/4
✓ Branch 0 taken 801 times.
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 103 times.
✓ Branch 3 taken 698 times.
911 for (int j = y; j < std::min(y + 8, shape.y) && !active; ++j) {
393
2/2
✓ Branch 0 taken 4057 times.
✓ Branch 1 taken 595 times.
4652 for (int k = z; k < std::min(z + 8, shape.z); ++k) {
394
2/2
✓ Branch 0 taken 3954 times.
✓ Branch 1 taken 103 times.
4057 if (at(i, j, k) != 0.0f) {
395 active = true;
396 break;
397 }
398 }
399 }
400 }
401
2/2
✓ Branch 0 taken 115 times.
✓ Branch 1 taken 7 times.
122 if (active) {
402
1/2
✓ Branch 2 taken 115 times.
✗ Branch 3 not taken.
115 leafIndex.emplace(glm::ivec3(x, y, z), leaves.size());
403
1/2
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
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
2/2
✓ Branch 1 taken 115 times.
✓ Branch 2 taken 4 times.
119 for (const glm::ivec3& leaf : leaves) {
413 115 const glm::ivec3 lower = nodeOrigin(leaf, 128);
414
3/4
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 111 times.
115 if (lowerIndex.emplace(lower, lowers.size()).second) {
415
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 lowers.push_back(lower);
416 }
417 115 const glm::ivec3 upper = nodeOrigin(leaf, 4096);
418
3/4
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 112 times.
115 if (upperIndex.emplace(upper, uppers.size()).second) {
419
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
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
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 std::vector<std::uint64_t> words(size / 8, 0);
433 4 const pnanovdb_buf_t buf =
434
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
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
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 112 times.
122 low = glm::min(low, other.low);
448
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 112 times.
122 high = glm::max(high, other.high);
449
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 112 times.
122 min = std::min(min, other.min);
450
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 96 times.
122 max = std::max(max, other.max);
451 122 }
452 };
453
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 std::vector<Stats> lowerStats(lowers.size());
454
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 std::vector<Stats> upperStats(uppers.size());
455 4 Stats rootStats;
456 4 std::uint64_t voxels = 0;
457
2/2
✓ Branch 0 taken 115 times.
✓ Branch 1 taken 4 times.
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
2/2
✓ Branch 0 taken 920 times.
✓ Branch 1 taken 115 times.
1035 for (int i = 0; i < 8; ++i) {
462
2/2
✓ Branch 0 taken 7360 times.
✓ Branch 1 taken 920 times.
8280 for (int j = 0; j < 8; ++j) {
463
2/2
✓ Branch 0 taken 58880 times.
✓ Branch 1 taken 7360 times.
66240 for (int k = 0; k < 8; ++k) {
464 58880 const glm::ivec3 ijk = origin8 + glm::ivec3(i, j, k);
465
2/2
✓ Branch 2 taken 28768 times.
✓ Branch 3 taken 30112 times.
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
4/4
✓ Branch 0 taken 13216 times.
✓ Branch 1 taken 16896 times.
✓ Branch 2 taken 10816 times.
✓ Branch 3 taken 2400 times.
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
1/2
✓ Branch 2 taken 115 times.
✗ Branch 3 not taken.
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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
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
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_offset_leaf(buf, treeHandle, leaves.empty() ? 0 : firstLeaf - tree);
574
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_offset_lower(buf, treeHandle, lowers.empty() ? 0 : firstLower - tree);
575
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_offset_upper(buf, treeHandle, uppers.empty() ? 0 : firstUpper - tree);
576
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_offset_root(buf, treeHandle, root - tree);
577
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_count_leaf(buf, treeHandle, static_cast<std::uint32_t>(leaves.size()));
578
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_count_lower(buf, treeHandle, static_cast<std::uint32_t>(lowers.size()));
579
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_node_count_upper(buf, treeHandle, static_cast<std::uint32_t>(uppers.size()));
580
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_tree_set_voxel_count(buf, treeHandle, voxels);
581
582 4 const pnanovdb_grid_handle_t grid{address(0)};
583
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_grid_set_magic(buf, grid, PNANOVDB_MAGIC_GRID);
584
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 4 times.
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
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 4 times.
16 for (std::uint32_t row = 0; row < 3; ++row) {
604
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 12 times.
48 for (std::uint32_t column = 0; column < 3; ++column) {
605
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 12 times.
36 const double scale = row == column ? voxelSize : 0.0;
606
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 24 times.
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
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_map_set_taperf(buf, map, 0, 1.0f);
617
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 pnanovdb_map_set_taperd(buf, map, 0, 1.0);
618
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 if (!leaves.empty()) {
619
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
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
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 4 times.
16 for (std::uint32_t a = 0; a < 3; ++a) {
627 12 pnanovdb_grid_set_voxel_size(buf, grid, a, voxelSize);
628 }
629
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 pnanovdb_grid_set_grid_class(buf, grid, PNANOVDB_GRID_CLASS_FOG_VOLUME);
630
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 pnanovdb_grid_set_grid_type(buf, grid, kType);
631
632
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
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
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
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