MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VDBArchive.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::IO::Detail {
6
7/**
8 * @struct VDBGridSpec
9 * @brief One grid's worth of input to VDBArchive::add_grid.
10 *
11 * Values are the active cells only, parallel to coords by position: element
12 * i of values belongs to coords[i]. Element width is four bytes for a scalar
13 * grid and twelve for a vector grid, so values.size() must equal coords.size()
14 * times that width.
15 *
16 * background is one element's worth of bytes, of the same width, used for
17 * every inactive cell and every tile.
18 *
19 * metadata entries are written verbatim as string-typed grid metadata. The
20 * caller owns those strings and they need only outlive the add_grid call.
21 * The grid name is passed separately and does not need repeating here.
22 */
24 std::string_view name;
25 std::span<const glm::ivec3> coords;
26 std::span<const std::byte> values;
27 std::span<const std::byte> background;
28 bool is_vector { false };
29 std::span<const std::pair<std::string, std::string>> metadata;
30};
31
32/**
33 * @struct VDBGridSummary
34 * @brief What a reader needs to know about one grid in an opened file,
35 * before deciding how to materialize it.
36 *
37 * voxel_size and translation come from the grid's own transform, read
38 * per-axis for SCALE_TRANSLATE (what VDBArchive::add_grid writes) and
39 * approximated for the rarer transform kinds a foreign file may carry — see
40 * the .cpp for the per-type fallback. active_min/active_max are the voxel-
41 * index bounding box of every leaf the tree holds, max exclusive, matching
42 * tinyvdb's own tvdb_grid_active_bbox convention. has_active is false, and
43 * the bbox members are unset, for a grid with no leaves at all.
44 *
45 * narrowed is true when the grid's leaf value type is something other than
46 * float (scalar) or vec3f (vector) — double, int32, int64, bool, vec3d,
47 * vec3i, or half — meaning VDBArchive::read_dense_scalar/read_dense_vector
48 * will convert every value to the representable type rather than reject it.
49 *
50 * background_scalar/background_vector are the root tile's fill value,
51 * narrowed the same way leaf values are. Only the one matching is_vector is
52 * meaningful; the other holds its default. This is the value every cell
53 * outside the tree takes — pass it to read_dense_scalar/read_dense_vector
54 * as the background argument to reproduce that.
55 */
57 std::string name;
58 bool is_vector { false };
59 bool narrowed { false };
60 glm::vec3 voxel_size { 1.0F };
61 glm::vec3 translation { 0.0F };
62 bool has_active { false };
63 glm::ivec3 active_min { 0 };
64 glm::ivec3 active_max { 0 }; ///< Exclusive.
65 float background_scalar { 0.0F };
66 glm::vec3 background_vector { 0.0F };
67};
68
69/**
70 * @class VDBArchive
71 * @brief RAII boundary around tinyvdb's C read and write paths.
72 *
73 * Every tvdb_ symbol, every manual allocation and every C-interop lint
74 * suppression in MayaFlux lives behind this class. Nothing above it includes
75 * a tinyvdb header, so a future change of backing library touches one
76 * translation unit.
77 *
78 * Write path: grids accumulate through add_grid and are written together by
79 * a single save, producing one .vdb carrying every field as a separately
80 * named grid. The destructor releases whatever was built regardless of
81 * whether save ran, so an abandoned archive and a failed one clean up
82 * identically.
83 *
84 * Read path: open() parses a file and reads every grid's tree into memory.
85 * grid_summary(), grid_metadata() and read_dense_scalar/read_dense_vector
86 * then address grids by index, in file order. An instance used for reading
87 * is not also used for writing; the two paths share the class only because
88 * they share the C boundary they hide.
89 *
90 * Not copyable and not movable: the underlying file struct holds a pointer
91 * into this object's own grid storage on the write side, and open() leaves
92 * live pointers into mapped file data on the read side.
93 */
95public:
96 VDBArchive();
98
99 VDBArchive(const VDBArchive&) = delete;
100 VDBArchive& operator=(const VDBArchive&) = delete;
103
104 /**
105 * @brief Build one grid from active cells and retain it for saving.
106 *
107 * The lattice becomes a per-axis scale-translate transform mapping index
108 * to world as cell_size() times index plus bounds.min plus half a cell,
109 * which reproduces Lattice3D::cell_center exactly.
110 *
111 * @return False if the spec is inconsistent or the tree build failed.
112 * Call last_error() for detail. A failure leaves previously added
113 * grids intact.
114 */
115 bool add_grid(const Kinesis::Lattice3D& lattice, const VDBGridSpec& spec);
116
117 /**
118 * @brief Write every added grid to one file.
119 *
120 * @param path Destination, already resolved by the caller.
121 * @param compression Combination of VDBCompression flags.
122 * @param level Deflate level for the ZIP path, ignored otherwise.
123 * @return False on failure; call last_error() for detail.
124 */
125 bool save(const std::string& path, uint32_t compression, int level);
126
127 [[nodiscard]] size_t grid_count() const;
128 [[nodiscard]] std::string_view last_error() const { return m_last_error; }
129
130 /**
131 * @brief Open a .vdb and read every grid's tree into memory.
132 *
133 * Replaces whatever a previous open() on this instance had loaded.
134 * Does not touch the write-side grid accumulation, so an instance is
135 * safe to open for reading even if add_grid was never called — the two
136 * are independent state.
137 *
138 * @param path Source path, already resolved by the caller.
139 * @return False on failure; call last_error() for detail.
140 */
141 bool open(const std::string& path);
142
143 /** @brief Number of grids in the opened file, or 0 if none is open. */
144 [[nodiscard]] size_t read_grid_count() const;
145
146 /**
147 * @brief Summarize one grid: name, value kind, transform, active bounds.
148 * @param index Grid index, less than read_grid_count().
149 * @return The summary, or a default-constructed one if index is out of
150 * range or nothing is open.
151 */
152 [[nodiscard]] VDBGridSummary grid_summary(size_t index) const;
153
154 /**
155 * @brief Look up a string-typed metadata entry on a grid.
156 * @param index Grid index, less than read_grid_count().
157 * @param key Metadata key, such as "class" or "vector_type".
158 * @return The value, or empty if absent, not a string, or index is out
159 * of range.
160 */
161 [[nodiscard]] std::string grid_metadata(size_t index, std::string_view key) const;
162
163 /**
164 * @brief Materialize a scalar grid's cells over an explicit region.
165 *
166 * region_min and resolution are expressed in the grid's own voxel-index
167 * space — the same space active_min/active_max in grid_summary() use.
168 * A cell not covered by any active leaf, and a cell whose index falls
169 * outside the region entirely, both take @p background. Output is
170 * resized to resolution.x*y*z elements and written x-fastest,
171 * z-slowest, matching Lattice3D and VolumeField's convention.
172 *
173 * A grid whose leaf value type is not float is converted per-element via
174 * MayaFlux::try_convert; check grid_summary().narrowed beforehand to
175 * know whether that happened at all, or pass @p precision_lost to learn
176 * whether it happened losslessly. Every element that round-trips through
177 * float and back to the source type unchanged leaves it false; a single
178 * element that doesn't sets it true for the whole call. half is the one
179 * source type never marked lossy — float has strictly more precision in
180 * both exponent and mantissa, so widening it is always exact.
181 *
182 * @param precision_lost Set to whether any narrowed element lost
183 * precision. Cleared to false at the start of the call if
184 * non-null; left untouched by a null pointer.
185 * @return False if index is out of range, the grid is vector-typed, or
186 * resolution has a zero axis. Call last_error() for detail.
187 */
189 size_t index,
190 const glm::ivec3& region_min,
191 const glm::uvec3& resolution,
192 float background,
193 std::vector<float>& out,
194 bool* precision_lost = nullptr) const;
195
196 /**
197 * @brief Materialize a vector grid's cells over an explicit region.
198 *
199 * As read_dense_scalar, for a grid whose leaf value type is vec3f,
200 * vec3d or vec3i. Non-vec3f types are converted component-wise via
201 * MayaFlux::try_convert; @p precision_lost is set if any of the three
202 * components of any element lost precision.
203 *
204 * @return False if index is out of range, the grid is scalar-typed, or
205 * resolution has a zero axis. Call last_error() for detail.
206 */
208 size_t index,
209 const glm::ivec3& region_min,
210 const glm::uvec3& resolution,
211 const glm::vec3& background,
212 std::vector<glm::vec3>& out,
213 bool* precision_lost = nullptr) const;
214
215private:
216 struct State;
217 std::unique_ptr<State> m_state;
218 mutable std::string m_last_error; ///< Set from the const read-path accessors too.
219};
220
221/**
222 * @brief Compression flags, mirroring tinyvdb's without exposing its header.
223 *
224 * Zip plus ActiveMask is the combination every DCC reads and the one that
225 * needs no LZ4 path. Blosc produces smaller files and is what recent OpenVDB
226 * writes by default.
227 */
228namespace VDBCompression {
229 inline constexpr uint32_t None = 0x0;
230 inline constexpr uint32_t Zip = 0x1;
231 inline constexpr uint32_t ActiveMask = 0x2;
232 inline constexpr uint32_t Blosc = 0x4;
233 inline constexpr uint32_t Default = Zip | ActiveMask;
234}
235
236} // namespace MayaFlux::IO::Detail
std::vector< float > * out
bool * precision_lost
glm::ivec3 region_min
uint32_t index
Definition VKDevice.cpp:142
bool save(const std::string &path, uint32_t compression, int level)
Write every added grid to one file.
VDBArchive & operator=(const VDBArchive &)=delete
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_view last_error() const
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.
VDBArchive(const VDBArchive &)=delete
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.
VDBArchive(VDBArchive &&)=delete
bool open(const std::string &path)
Open a .vdb and read every grid's tree into memory.
VDBArchive & operator=(VDBArchive &&)=delete
size_t read_grid_count() const
Number of grids in the opened file, or 0 if none is open.
RAII boundary around tinyvdb's C read and write paths.
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