MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VDBArchive.cpp
Go to the documentation of this file.
1#include "VDBArchive.hpp"
2
3extern "C" {
4#include "tinyvdb_ray.h"
5#include "tinyvdb_sparse_tree.h"
6}
7
8#include <deque>
9
11
12namespace {
13
14 // NOLINTBEGIN(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory)
15 // tinyvdb takes its allocator as three C function pointers. A container
16 // or smart pointer cannot be substituted at this boundary.
17
18 void* sys_malloc(size_t size, void* /*user_ctx*/)
19 {
20 return std::malloc(size);
21 }
22
23 void* sys_realloc(void* ptr, size_t /*old_size*/, size_t size, void* /*user_ctx*/)
24 {
25 return std::realloc(ptr, size);
26 }
27
28 void sys_free(void* ptr, size_t /*size*/, void* /*user_ctx*/)
29 {
30 std::free(ptr);
31 }
32
33 // NOLINTEND(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory)
34
35 /**
36 * @brief Allocator handed to the writer.
37 *
38 * tvdb_file_t carries its own allocator and the write path dereferences
39 * it without a null check, so a zeroed struct faults on the first stream
40 * write rather than falling back to malloc.
41 */
42 tvdb_allocator_t make_allocator()
43 {
44 tvdb_allocator_t alloc;
45 std::memset(&alloc, 0, sizeof(alloc));
46 alloc.malloc_fn = &sys_malloc;
47 alloc.realloc_fn = &sys_realloc;
48 alloc.free_fn = &sys_free;
49 alloc.user_ctx = nullptr;
50 return alloc;
51 }
52
53 constexpr const char* grid_type_token(bool is_vector)
54 {
55 return is_vector ? "Tree_vec3s_5_4_3" : "Tree_float_5_4_3";
56 }
57
58 // -------------------------------------------------------------------------
59 // Read path
60 // -------------------------------------------------------------------------
61
62 /**
63 * @brief The value type leaf-level cells of a grid are stored as.
64 */
65 tvdb_value_type_t leaf_value_type(const tvdb_grid_t& grid)
66 {
67 const int levels = grid.tree.layout.num_levels;
68 if (levels <= 0) {
69 return TVDB_VALUE_NULL;
70 }
71 return grid.tree.layout.levels[levels - 1].value_type;
72 }
73
74 constexpr bool is_vector_type(tvdb_value_type_t vt)
75 {
76 return vt == TVDB_VALUE_VEC3F || vt == TVDB_VALUE_VEC3D || vt == TVDB_VALUE_VEC3I;
77 }
78
79 constexpr bool needs_narrowing(tvdb_value_type_t vt)
80 {
81 return vt != TVDB_VALUE_FLOAT && vt != TVDB_VALUE_VEC3F;
82 }
83
84 /**
85 * @brief Per-axis voxel size from a grid's transform.
86 *
87 * SCALE_TRANSLATE — what add_grid writes — carries independent per-axis
88 * values directly. AFFINE, which no MayaFlux-written file uses, falls
89 * back to the matrix diagonal: exact for an axis-aligned scale, wrong
90 * for a sheared or rotated one, which this reader does not attempt to
91 * represent.
92 */
93 glm::vec3 grid_voxel_size(const tvdb_transform_t& t)
94 {
95 switch (t.type) {
96 case TVDB_TRANSFORM_UNIFORM_SCALE:
97 case TVDB_TRANSFORM_UNIFORM_SCALE_TRANSLATE:
98 return glm::vec3(static_cast<float>(t.voxel_size[0]));
99 case TVDB_TRANSFORM_SCALE:
100 case TVDB_TRANSFORM_SCALE_TRANSLATE:
101 return {
102 static_cast<float>(t.voxel_size[0]),
103 static_cast<float>(t.voxel_size[1]),
104 static_cast<float>(t.voxel_size[2]),
105 };
106 case TVDB_TRANSFORM_AFFINE:
107 return {
108 static_cast<float>(t.matrix[0][0]),
109 static_cast<float>(t.matrix[1][1]),
110 static_cast<float>(t.matrix[2][2]),
111 };
112 case TVDB_TRANSFORM_TRANSLATION:
113 default:
114 return glm::vec3(1.0F);
115 }
116 }
117
118 /**
119 * @brief World-space translation from a grid's transform.
120 *
121 * AFFINE's translation is read from the last column, rows 0-2 — the
122 * standard [R | t; 0 0 0 1] row-major layout, confirmed directly
123 * against tinyvdb's own AffineMap read/write (tvdb->translation[i] =
124 * matrix[i][3], both directions), not assumed.
125 */
126 glm::vec3 grid_translation(const tvdb_transform_t& t)
127 {
128 switch (t.type) {
129 case TVDB_TRANSFORM_UNIFORM_SCALE_TRANSLATE:
130 case TVDB_TRANSFORM_SCALE_TRANSLATE:
131 case TVDB_TRANSFORM_TRANSLATION:
132 return {
133 static_cast<float>(t.translation[0]),
134 static_cast<float>(t.translation[1]),
135 static_cast<float>(t.translation[2]),
136 };
137 case TVDB_TRANSFORM_AFFINE:
138 return {
139 static_cast<float>(t.matrix[0][3]),
140 static_cast<float>(t.matrix[1][3]),
141 static_cast<float>(t.matrix[2][3]),
142 };
143 default:
144 return glm::vec3(0.0F);
145 }
146 }
147
148 /**
149 * @brief IEEE 754 binary16 to binary32. tinyvdb decompresses on-disk half
150 * storage transparently, so this path is defensive: it only fires
151 * if a leaf's own declared value type is half, which no format
152 * this reader has seen in practice produces.
153 */
154 float half_to_float(uint16_t h)
155 {
156 const uint32_t sign = static_cast<uint32_t>(h & 0x8000U) << 16;
157 uint32_t exp = (h >> 10) & 0x1FU;
158 uint32_t mant = h & 0x3FFU;
159 uint32_t bits {};
160
161 if (exp == 0) {
162 if (mant == 0) {
163 bits = sign;
164 } else {
165 exp = 1;
166 while ((mant & 0x400U) == 0) {
167 mant <<= 1;
168 --exp;
169 }
170 mant &= 0x3FFU;
171 bits = sign | ((exp + 112U) << 23) | (mant << 13);
172 }
173 } else if (exp == 0x1FU) {
174 bits = sign | 0x7F800000U | (mant << 13);
175 } else {
176 bits = sign | ((exp + 112U) << 23) | (mant << 13);
177 }
178
179 float f {};
180 std::memcpy(&f, &bits, sizeof(f));
181 return f;
182 }
183
184 /**
185 * @brief Convert one raw scalar element to float via MayaFlux::try_convert.
186 *
187 * HALF is the one source type try_convert cannot see: it is a bit
188 * pattern, not a type try_convert's arithmetic concepts recognise, so
189 * it keeps its own decode. It is also the one case with no precision
190 * question to answer — float has strictly more range and mantissa bits
191 * than half, so widening it is always exact.
192 */
193 CastResult<float> narrow_scalar(tvdb_value_type_t vt, const void* bytes)
194 {
195 switch (vt) {
196 case TVDB_VALUE_FLOAT: {
197 float v {};
198 std::memcpy(&v, bytes, sizeof(v));
199 return try_convert<float>(v);
200 }
201 case TVDB_VALUE_DOUBLE: {
202 double v {};
203 std::memcpy(&v, bytes, sizeof(v));
204 return try_convert<float>(v);
205 }
206 case TVDB_VALUE_INT32: {
207 int32_t v {};
208 std::memcpy(&v, bytes, sizeof(v));
209 return try_convert<float>(v);
210 }
211 case TVDB_VALUE_INT64: {
212 int64_t v {};
213 std::memcpy(&v, bytes, sizeof(v));
214 return try_convert<float>(v);
215 }
216 case TVDB_VALUE_BOOL: {
217 uint8_t v {};
218 std::memcpy(&v, bytes, sizeof(v));
219 return try_convert<float>(v != 0);
220 }
221 case TVDB_VALUE_HALF: {
222 uint16_t v {};
223 std::memcpy(&v, bytes, sizeof(v));
224 CastResult<float> result;
225 result.value = half_to_float(v);
226 return result;
227 }
228 default: {
229 CastResult<float> result;
230 result.value = 0.0F;
231 return result;
232 }
233 }
234 }
235
236 /**
237 * @brief narrow_vector's result: the converted glm::vec3 and whether
238 * any of its three components lost precision.
239 *
240 * A plain glm::vec3 cannot also carry precision_loss, and
241 * try_convert does not itself understand GLM types (it
242 * converts one arithmetic scalar at a time) so this aggregates three
243 * per-component try_convert calls rather than making one call over the
244 * vector as a whole.
245 */
246 struct VectorNarrowResult {
247 glm::vec3 value { 0.0F };
248 bool precision_loss { false };
249 };
250
251 VectorNarrowResult narrow_vector(tvdb_value_type_t vt, const void* bytes)
252 {
253 switch (vt) {
254 case TVDB_VALUE_VEC3F: {
255 float v[3];
256 std::memcpy(v, bytes, sizeof(v));
257 return { { v[0], v[1], v[2] }, false };
258 }
259 case TVDB_VALUE_VEC3D: {
260 double v[3];
261 std::memcpy(v, bytes, sizeof(v));
262 const auto cx = try_convert<float>(v[0]);
263 const auto cy = try_convert<float>(v[1]);
264 const auto cz = try_convert<float>(v[2]);
265 return {
266 { cx.value.value_or(0.0F), cy.value.value_or(0.0F), cz.value.value_or(0.0F) },
267 cx.precision_loss || cy.precision_loss || cz.precision_loss,
268 };
269 }
270 case TVDB_VALUE_VEC3I: {
271 int32_t v[3];
272 std::memcpy(v, bytes, sizeof(v));
273 const auto cx = try_convert<float>(v[0]);
274 const auto cy = try_convert<float>(v[1]);
275 const auto cz = try_convert<float>(v[2]);
276 return {
277 { cx.value.value_or(0.0F), cy.value.value_or(0.0F), cz.value.value_or(0.0F) },
278 cx.precision_loss || cy.precision_loss || cz.precision_loss,
279 };
280 }
281 default:
282 return {};
283 }
284 }
285
286 /**
287 * @brief The root tile's background value, narrowed to float.
288 *
289 * Root index 0 is not a convention this reader invents: tinyvdb's own
290 * tvdb_grid_set_background writes through tree.nodes[0].u.root, so a
291 * well-formed grid always has its root there. Background precision loss
292 * is not tracked separately from the active-cell kind read_dense_scalar/
293 * read_dense_vector report; a lossy background is rare enough (it is one
294 * value, not a whole grid's worth) that this returns the converted value
295 * only.
296 */
297 float grid_background_scalar(const tvdb_grid_t& grid)
298 {
299 if (grid.tree.num_nodes == 0) {
300 return 0.0F;
301 }
302 const tvdb_value_t& bg = grid.tree.nodes[0].u.root.background;
303 return narrow_scalar(bg.type, &bg.u).value.value_or(0.0F);
304 }
305
306 glm::vec3 grid_background_vector(const tvdb_grid_t& grid)
307 {
308 if (grid.tree.num_nodes == 0) {
309 return glm::vec3(0.0F);
310 }
311 const tvdb_value_t& bg = grid.tree.nodes[0].u.root.background;
312 return narrow_vector(bg.type, &bg.u).value;
313 }
314
315 /**
316 * @brief Union of every leaf's voxel-index extent, tinyvdb's own
317 * active-bbox definition, generalized past its float-only
318 * tvdb_grid_active_bbox.
319 */
320 struct BBoxAcc {
321 glm::ivec3 min { 0 };
322 glm::ivec3 max { 0 };
323 bool has_any { false };
324 };
325
326 int bbox_visit(const tvdb_leaf_view_t* leaf, void* user)
327 {
328 auto* acc = static_cast<BBoxAcc*>(user);
329 const int32_t dim = 1 << leaf->log2dim;
330 const glm::ivec3 lo(leaf->origin[0], leaf->origin[1], leaf->origin[2]);
331 const glm::ivec3 hi = lo + glm::ivec3(dim);
332
333 if (!acc->has_any) {
334 acc->min = lo;
335 acc->max = hi;
336 acc->has_any = true;
337 } else {
338 acc->min = glm::min(acc->min, lo);
339 acc->max = glm::max(acc->max, hi);
340 }
341 return 0;
342 }
343
344 /**
345 * @brief Shared context for the dense-fill visitors.
346 *
347 * region_max is exclusive. elem_size is the leaf's own on-disk element
348 * width, used to stride into the raw byte buffer regardless of what
349 * that element narrows to. precision_lost, when non-null, accumulates
350 * across every element the visit touches — set true the first time any
351 * one of them loses precision and left alone afterward.
352 */
353 struct DenseCtx {
354 glm::ivec3 region_min;
355 glm::ivec3 region_max;
356 tvdb_value_type_t vt;
357 size_t elem_size;
358 bool* precision_lost { nullptr };
359 };
360
361 struct DenseScalarCtx : DenseCtx {
362 std::vector<float>* out;
363 };
364
365 struct DenseVectorCtx : DenseCtx {
366 std::vector<glm::vec3>* out;
367 };
368
369 /**
370 * @brief World-voxel coordinate of leaf slot s, OpenVDB's own
371 * (x<<2L)|(y<<L)|z packing within a dim^3 block.
372 */
373 glm::ivec3 leaf_slot_coord(const tvdb_leaf_view_t& leaf, int32_t s)
374 {
375 const int32_t log2dim = leaf.log2dim;
376 const int32_t mask = (1 << log2dim) - 1;
377 return {
378 leaf.origin[0] + ((s >> (2 * log2dim)) & mask),
379 leaf.origin[1] + ((s >> log2dim) & mask),
380 leaf.origin[2] + (s & mask),
381 };
382 }
383
384 int dense_scalar_visit(const tvdb_leaf_view_t* leaf, void* user)
385 {
386 auto* ctx = static_cast<DenseScalarCtx*>(user);
387 const int32_t nslots = 1 << (3 * leaf->log2dim);
388 const auto* base = reinterpret_cast<const uint8_t*>(leaf->data);
389 const glm::ivec3 res = ctx->region_max - ctx->region_min;
390
391 for (int32_t s = 0; s < nslots; ++s) {
392 if (!tvdb_nodemask_is_on(leaf->value_mask, s)) {
393 continue;
394 }
395 const glm::ivec3 world = leaf_slot_coord(*leaf, s);
396 if (glm::any(glm::lessThan(world, ctx->region_min))
397 || glm::any(glm::greaterThanEqual(world, ctx->region_max))) {
398 continue;
399 }
400 const glm::ivec3 local = world - ctx->region_min;
401 const size_t index = (static_cast<size_t>(local.z) * res.y + local.y) * res.x + local.x;
402 const auto converted = narrow_scalar(ctx->vt, base + static_cast<size_t>(s) * ctx->elem_size);
403 (*ctx->out)[index] = converted.value.value_or(0.0F);
404 if (converted.precision_loss && ctx->precision_lost) {
405 *ctx->precision_lost = true;
406 }
407 }
408 return 0;
409 }
410
411 int dense_vector_visit(const tvdb_leaf_view_t* leaf, void* user)
412 {
413 auto* ctx = static_cast<DenseVectorCtx*>(user);
414 const int32_t nslots = 1 << (3 * leaf->log2dim);
415 const auto* base = reinterpret_cast<const uint8_t*>(leaf->data);
416 const glm::ivec3 res = ctx->region_max - ctx->region_min;
417
418 for (int32_t s = 0; s < nslots; ++s) {
419 if (!tvdb_nodemask_is_on(leaf->value_mask, s)) {
420 continue;
421 }
422 const glm::ivec3 world = leaf_slot_coord(*leaf, s);
423 if (glm::any(glm::lessThan(world, ctx->region_min))
424 || glm::any(glm::greaterThanEqual(world, ctx->region_max))) {
425 continue;
426 }
427 const glm::ivec3 local = world - ctx->region_min;
428 const size_t index = (static_cast<size_t>(local.z) * res.y + local.y) * res.x + local.x;
429 const auto converted = narrow_vector(ctx->vt, base + static_cast<size_t>(s) * ctx->elem_size);
430 (*ctx->out)[index] = converted.value;
431 if (converted.precision_loss && ctx->precision_lost) {
432 *ctx->precision_lost = true;
433 }
434 }
435 return 0;
436 }
437
438 constexpr size_t element_width(bool is_vector)
439 {
440 return is_vector ? sizeof(glm::vec3) : sizeof(float);
441 }
442
443 /**
444 * @brief Synthesize the 5-4-3 template the builder reads layout, grid
445 * type and transform from.
446 *
447 * The template's own tree is unused. grid_type must outlive the builder
448 * call; the builder duplicates the string but borrows it until it does.
449 */
450 tvdb_grid_t make_template(
451 const Kinesis::Lattice3D& lattice, bool is_vector, char* grid_type)
452 {
453 tvdb_grid_t tmpl;
454 std::memset(&tmpl, 0, sizeof(tmpl));
455
456 std::strcpy(grid_type, grid_type_token(is_vector));
457 tmpl.descriptor.grid_type = grid_type;
458
459 const tvdb_value_type_t vt = is_vector ? TVDB_VALUE_VEC3F : TVDB_VALUE_FLOAT;
460
461 tmpl.tree.layout.num_levels = 4;
462 tmpl.tree.layout.levels[0].node_type = TVDB_NODE_ROOT;
463 tmpl.tree.layout.levels[1].node_type = TVDB_NODE_INTERNAL;
464 tmpl.tree.layout.levels[2].node_type = TVDB_NODE_INTERNAL;
465 tmpl.tree.layout.levels[3].node_type = TVDB_NODE_LEAF;
466 tmpl.tree.layout.levels[0].log2dim = 0;
467 tmpl.tree.layout.levels[1].log2dim = 5;
468 tmpl.tree.layout.levels[2].log2dim = 4;
469 tmpl.tree.layout.levels[3].log2dim = 3;
470 for (int lv = 0; lv < 4; ++lv) {
471 tmpl.tree.layout.levels[lv].value_type = vt;
472 }
473
474 const glm::vec3 cell = lattice.cell_size();
475 const glm::vec3 origin = lattice.bounds.min + 0.5F * cell;
476
477 tmpl.transform.type = TVDB_TRANSFORM_SCALE_TRANSLATE;
478 for (int axis = 0; axis < 3; ++axis) {
479 tmpl.transform.scale_values[axis] = cell[axis];
480 tmpl.transform.voxel_size[axis] = cell[axis];
481 tmpl.transform.translation[axis] = origin[axis];
482 }
483
484 return tmpl;
485 }
486
487} // namespace
488
489/**
490 * @struct VDBArchive::State
491 * @brief Built grids and the storage their borrowed pointers refer into.
492 *
493 * tvdb_meta_entry_t holds bare char pointers and tvdb_grid_destroy_owned
494 * frees whatever the grid owns. Metadata strings therefore live here and the
495 * entry array is detached from each grid before destroying it, so tinyvdb
496 * never frees memory it did not allocate.
497 *
498 * Deques rather than vectors: entry arrays hold pointers into the string
499 * storage, and grids hold pointers into the entry storage, so neither may
500 * reallocate as more grids are added.
501 *
502 * read_file/read_file_open are the read path's entire state: open() parses
503 * a file into read_file and every accessor below indexes into it directly.
504 * Independent of the write-side members above — an instance may open() for
505 * reading without ever having called add_grid.
506 */
508 std::vector<tvdb_grid_t> grids;
509 std::deque<std::string> strings;
510 std::deque<std::vector<tvdb_meta_entry_t>> entries;
511 std::deque<std::array<char, 32>> type_tokens;
512
513 tvdb_file_t read_file {};
514 bool read_file_open { false };
515};
516
518 : m_state(std::make_unique<State>())
519{
520}
521
523{
524 for (auto& grid : m_state->grids) {
525 grid.metadata.entries = nullptr;
526 grid.metadata.count = 0;
527 grid.metadata.capacity = 0;
528 tvdb_grid_destroy_owned(&grid);
529 }
530
531 if (m_state->read_file_open) {
532 tvdb_file_close(&m_state->read_file);
533 }
534}
535
537{
538 return m_state->grids.size();
539}
540
542 const Kinesis::Lattice3D& lattice, const VDBGridSpec& spec)
543{
544 const size_t width = element_width(spec.is_vector);
545 if (spec.values.size() != spec.coords.size() * width) {
546 m_last_error = "grid '" + std::string(spec.name)
547 + "': value bytes do not match coordinate count";
548 return false;
549 }
550 if (spec.background.size() != width) {
551 m_last_error = "grid '" + std::string(spec.name)
552 + "': background is the wrong width";
553 return false;
554 }
555
556 static_assert(sizeof(glm::ivec3) == sizeof(tvdb_vec3i),
557 "glm::ivec3 and tvdb_vec3i must be layout compatible for the "
558 "coordinate span to be passed through without a copy");
559
560 auto& type_token = m_state->type_tokens.emplace_back();
561 tvdb_grid_t tmpl = make_template(lattice, spec.is_vector, type_token.data());
562
563 const std::string name(spec.name);
564
565 tvdb_grid_t grid;
566 const bool ok = tvdb_grid_from_sparse_typed_using_template(
567 &tmpl,
568 reinterpret_cast<const tvdb_vec3i*>(spec.coords.data()),
569 spec.values.data(),
570 spec.coords.size(),
571 spec.is_vector ? TVDB_VALUE_VEC3F : TVDB_VALUE_FLOAT,
572 spec.background.data(),
573 name.c_str(),
574 &grid);
575
576 if (!ok) {
577 m_last_error = "grid '" + name + "': tree build failed";
578 return false;
579 }
580
581 auto& list = m_state->entries.emplace_back();
582 list.reserve(spec.metadata.size() + 1);
583
584 auto add_entry = [&](std::string_view key, std::string_view value) {
585 std::string& k = m_state->strings.emplace_back(key);
586 std::string& t = m_state->strings.emplace_back("string");
587 std::string& v = m_state->strings.emplace_back(value);
588
589 tvdb_meta_entry_t entry;
590 std::memset(&entry, 0, sizeof(entry));
591 entry.name = k.data();
592 entry.type_name = t.data();
593 entry.value.type = TVDB_VALUE_STRING;
594 entry.value.u.s.str = v.data();
595 entry.value.u.s.len = v.size();
596 list.push_back(entry);
597 };
598
599 // OpenVDB reads a grid's name from metadata, not from the archive's grid
600 // descriptor. Without this entry vdb_print and every DCC show it unnamed.
601 add_entry("name", spec.name);
602
603 for (const auto& [key, value] : spec.metadata) {
604 add_entry(key, value);
605 }
606
607 grid.metadata.entries = list.data();
608 grid.metadata.count = list.size();
609 grid.metadata.capacity = list.size();
610 grid.metadata.alloc = nullptr;
611
612 m_state->grids.push_back(grid);
613 return true;
614}
615
616bool VDBArchive::save(const std::string& path, uint32_t compression, int level)
617{
618 if (m_state->grids.empty()) {
619 m_last_error = "no grids to save";
620 return false;
621 }
622
623 tvdb_file_t out;
624 std::memset(&out, 0, sizeof(out));
625 out.alloc = make_allocator();
626 out.num_grids = m_state->grids.size();
627 out.grids = m_state->grids.data();
628
629 tvdb_error_t err;
630 std::memset(&err, 0, sizeof(err));
631
632 const tvdb_status_t st = tvdb_file_save(
633 &out, path.c_str(), compression, level, /*use_mmap=*/0, &err);
634
635 if (st != TVDB_OK) {
636 m_last_error = std::string("save failed: ")
637 + (err.message[0] != '\0' ? err.message : "unknown");
638 return false;
639 }
640
641 return true;
642}
643
644// =============================================================================
645// Read path
646// =============================================================================
647
648bool VDBArchive::open(const std::string& path)
649{
650 if (m_state->read_file_open) {
651 tvdb_file_close(&m_state->read_file);
652 m_state->read_file_open = false;
653 }
654
655 tvdb_error_t err;
656 std::memset(&err, 0, sizeof(err));
657
658 if (tvdb_file_open(&m_state->read_file, path.c_str(), nullptr, &err) != TVDB_OK) {
659 m_last_error = std::string("open failed: ")
660 + (err.message[0] != '\0' ? err.message : "unknown");
661 return false;
662 }
663 m_state->read_file_open = true;
664
665 if (tvdb_read_all_grids(&m_state->read_file, &err) != TVDB_OK) {
666 m_last_error = std::string("grid read failed: ")
667 + (err.message[0] != '\0' ? err.message : "unknown");
668 tvdb_file_close(&m_state->read_file);
669 m_state->read_file_open = false;
670 return false;
671 }
672
673 return true;
674}
675
677{
678 return m_state->read_file_open ? tvdb_grid_count(&m_state->read_file) : 0;
679}
680
682{
684
685 if (!m_state->read_file_open || index >= m_state->read_file.num_grids) {
686 return out;
687 }
688
689 const tvdb_grid_t& grid = m_state->read_file.grids[index];
690
691 const char* name = tvdb_grid_name(&m_state->read_file, index);
692 out.name = name ? name : "";
693
694 const tvdb_value_type_t vt = leaf_value_type(grid);
695 out.is_vector = is_vector_type(vt);
696 out.narrowed = needs_narrowing(vt);
697
698 out.voxel_size = grid_voxel_size(grid.transform);
699 out.translation = grid_translation(grid.transform);
700
701 BBoxAcc acc;
702 tvdb_grid_visit_leaves(&grid, bbox_visit, &acc);
703 out.has_active = acc.has_any;
704 out.active_min = acc.min;
705 out.active_max = acc.max;
706
707 out.background_scalar = grid_background_scalar(grid);
708 out.background_vector = grid_background_vector(grid);
709
710 return out;
711}
712
713std::string VDBArchive::grid_metadata(size_t index, std::string_view key) const
714{
715 if (!m_state->read_file_open || index >= m_state->read_file.num_grids) {
716 return {};
717 }
718
719 const tvdb_grid_t& grid = m_state->read_file.grids[index];
720
721 for (size_t i = 0; i < grid.metadata.count; ++i) {
722 const tvdb_meta_entry_t& entry = grid.metadata.entries[i];
723 if (!entry.name || key != entry.name) {
724 continue;
725 }
726 if (entry.value.type != TVDB_VALUE_STRING || !entry.value.u.s.str) {
727 return {};
728 }
729 return { entry.value.u.s.str, entry.value.u.s.len };
730 }
731 return {};
732}
733
735 size_t index,
736 const glm::ivec3& region_min,
737 const glm::uvec3& resolution,
738 float background,
739 std::vector<float>& out,
740 bool* precision_lost) const
741{
742 if (!m_state->read_file_open || index >= m_state->read_file.num_grids) {
743 m_last_error = "read_dense_scalar: grid index out of range";
744 return false;
745 }
746 if (resolution.x == 0 || resolution.y == 0 || resolution.z == 0) {
747 m_last_error = "read_dense_scalar: zero resolution";
748 return false;
749 }
750
751 const tvdb_grid_t& grid = m_state->read_file.grids[index];
752 const tvdb_value_type_t vt = leaf_value_type(grid);
753
754 if (is_vector_type(vt)) {
755 const char* name = tvdb_grid_name(&m_state->read_file, index);
756 m_last_error = "read_dense_scalar: grid '" + std::string(name ? name : "") + "' is vector-typed";
757 return false;
758 }
759 const size_t elem_size = tvdb_value_type_size(vt);
760 if (elem_size == 0) {
761 m_last_error = "read_dense_scalar: grid has an unsupported leaf value type";
762 return false;
763 }
764
765 if (precision_lost) {
766 *precision_lost = false;
767 }
768
769 out.assign(static_cast<size_t>(resolution.x) * resolution.y * resolution.z, background);
770
771 DenseScalarCtx ctx {};
772 ctx.region_min = region_min;
773 ctx.region_max = region_min + glm::ivec3(resolution);
774 ctx.vt = vt;
775 ctx.elem_size = elem_size;
776 ctx.precision_lost = precision_lost;
777 ctx.out = &out;
778
779 tvdb_grid_visit_leaves(&grid, dense_scalar_visit, &ctx);
780 return true;
781}
782
784 size_t index,
785 const glm::ivec3& region_min,
786 const glm::uvec3& resolution,
787 const glm::vec3& background,
788 std::vector<glm::vec3>& out,
789 bool* precision_lost) const
790{
791 if (!m_state->read_file_open || index >= m_state->read_file.num_grids) {
792 m_last_error = "read_dense_vector: grid index out of range";
793 return false;
794 }
795 if (resolution.x == 0 || resolution.y == 0 || resolution.z == 0) {
796 m_last_error = "read_dense_vector: zero resolution";
797 return false;
798 }
799
800 const tvdb_grid_t& grid = m_state->read_file.grids[index];
801 const tvdb_value_type_t vt = leaf_value_type(grid);
802
803 if (!is_vector_type(vt)) {
804 const char* name = tvdb_grid_name(&m_state->read_file, index);
805 m_last_error = "read_dense_vector: grid '" + std::string(name ? name : "") + "' is scalar-typed";
806 return false;
807 }
808 const size_t elem_size = tvdb_value_type_size(vt);
809 if (elem_size == 0) {
810 m_last_error = "read_dense_vector: grid has an unsupported leaf value type";
811 return false;
812 }
813
814 if (precision_lost) {
815 *precision_lost = false;
816 }
817
818 out.assign(static_cast<size_t>(resolution.x) * resolution.y * resolution.z, background);
819
820 DenseVectorCtx ctx {};
821 ctx.region_min = region_min;
822 ctx.region_max = region_min + glm::ivec3(resolution);
823 ctx.vt = vt;
824 ctx.elem_size = elem_size;
825 ctx.precision_lost = precision_lost;
826 ctx.out = &out;
827
828 tvdb_grid_visit_leaves(&grid, dense_vector_visit, &ctx);
829 return true;
830}
831
832} // namespace MayaFlux::IO::Detail
uint32_t h
Definition InkPress.cpp:29
glm::ivec3 min
glm::ivec3 max
glm::ivec3 region_max
std::vector< float > * out
bool * precision_lost
glm::vec3 value
glm::ivec3 region_min
tvdb_value_type_t vt
bool precision_loss
size_t elem_size
bool has_any
bool is_vector
Definition VDBWriter.cpp:27
std::string name
Definition VKDevice.cpp:143
uint32_t index
Definition VKDevice.cpp:142
const uint8_t * ptr
float lo
float k
float hi
uint32_t width
bool save(const std::string &path, uint32_t compression, int level)
Write every added grid to one file.
std::string grid_metadata(size_t index, std::string_view key) const
Look up a string-typed metadata entry on a grid.
bool read_dense_scalar(size_t index, const glm::ivec3 &region_min, const glm::uvec3 &resolution, float background, std::vector< float > &out, bool *precision_lost=nullptr) const
Materialize a scalar grid's cells over an explicit region.
std::string m_last_error
Set from the const read-path accessors too.
std::unique_ptr< State > m_state
bool read_dense_vector(size_t index, const glm::ivec3 &region_min, const glm::uvec3 &resolution, const glm::vec3 &background, std::vector< glm::vec3 > &out, bool *precision_lost=nullptr) const
Materialize a vector grid's cells over an explicit region.
VDBGridSummary grid_summary(size_t index) const
Summarize one grid: name, value kind, transform, active bounds.
bool add_grid(const Kinesis::Lattice3D &lattice, const VDBGridSpec &spec)
Build one grid from active cells and retain it for saving.
bool open(const std::string &path)
Open a .vdb and read every grid's tree into memory.
size_t read_grid_count() const
Number of grids in the opened file, or 0 if none is open.
std::deque< std::string > strings
std::deque< std::vector< tvdb_meta_entry_t > > entries
std::vector< tvdb_grid_t > grids
std::deque< std::array< char, 32 > > type_tokens
Built grids and the storage their borrowed pointers refer into.
std::span< const std::byte > values
std::span< const glm::ivec3 > coords
std::span< const std::byte > background
std::span< const std::pair< std::string, std::string > > metadata
One grid's worth of input to VDBArchive::add_grid.
What a reader needs to know about one grid in an opened file, before deciding how to materialize it.
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25