MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeReader.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::IO::Detail {
7class VDBArchive;
8}
9
10namespace MayaFlux::IO {
11
12/**
13 * @struct VolumeReadOptions
14 * @brief Configuration for volume reading.
15 */
17 /**
18 * @brief Grids to load, matched by name. Empty loads every grid in the
19 * file, in file order.
20 *
21 * A name with no matching grid is logged and skipped; the rest of the
22 * selection proceeds.
23 */
24 std::vector<std::string> field_names;
25};
26
27/**
28 * @class VolumeReader
29 * @brief tinyvdb-backed loader for volumetric grid files.
30 *
31 * Parallels ModelReader: a FileReader subclass whose primary API is
32 * open()+extract() or the one-shot load(), producing Kakshya::VolumeData —
33 * one lattice and every selected grid as a named VolumeField over it.
34 * create_container() and load_into_container() are no-ops, as for
35 * ModelReader; volume data does not go through the SignalSourceContainer
36 * streaming path.
37 *
38 * Supported formats: .vdb, via Detail::VDBArchive's read path.
39 *
40 * ## Lattice reconstruction
41 *
42 * A .vdb carries a transform and, per grid, an active-voxel bounding box —
43 * not a resolution and world bounds the way Lattice3D wants. VolumeData
44 * needs one lattice shared by every field, so this reader offers two ways
45 * to get one:
46 *
47 * - extract()/load() with no lattice: the output lattice is the union of
48 * every selected grid's active bbox, in voxel-index space, with voxel
49 * size and translation taken from the first selected grid's transform.
50 * This is what a DCC user expects — the file's own content decides the
51 * size — and is exact when every grid in the file shares one transform,
52 * which every file MayaFlux writes does and most single-purpose exports
53 * from another DCC do too.
54 * - extract()/load() with a caller-supplied Lattice3D: region_min is
55 * derived from the lattice's world bounds through the *first selected
56 * grid's* transform, snapped to the nearest voxel index — no
57 * interpolation. This is the only path that round-trips exactly: pass
58 * the same Lattice3D a VDBWriter call was given and the cell values come
59 * back unpermuted and unresampled, which is what verify_volume_export's
60 * round-trip check exercises.
61 *
62 * Both paths assume every selected grid shares the first grid's voxel size
63 * and translation. A file with per-grid transforms that actually differ is
64 * not resampled into agreement — each grid's raw voxel indices are read
65 * directly against the shared region, which misplaces that grid's content
66 * relative to the others. materialize() logs an MF_WARN naming the
67 * mismatched grid so this is a loud failure rather than a silent one, but
68 * it does not correct it — actual per-grid resampling would need to
69 * materialize the mismatched grid separately in its own index space and
70 * interpolate into the shared lattice, which is not implemented. This is a
71 * real limitation for a file assembled by hand from mismatched sources; it
72 * is not a limitation for output produced by a single simulation or DCC
73 * export, which is what this reader exists to consume.
74 *
75 * ## Inactive cells
76 *
77 * VolumeData is dense: every cell in the output lattice that is not covered
78 * by an active leaf takes the grid's own background value (root.background
79 * in exchange), narrowed the same way an active cell's value is if the
80 * grid's own type needs it — see below. This means a sparse simulation
81 * export expands to full density in host memory: six fields at 256 cubed
82 * is roughly 400 MB, before whatever the caller does with it next. A tile
83 * whose inactive fill differs from the plain background — a level set's
84 * sign-flood-filled interior/exterior tiles are the standard example — is
85 * not reconstructed; every non-leaf cell reads as the one background value
86 * regardless of which side of the surface it is on. A foreign narrow-band
87 * level set is therefore the case most likely to come back wrong; a fog
88 * volume or a carried scalar, where every non-leaf cell genuinely is the
89 * background, round-trips correctly.
90 *
91 * ## Type narrowing
92 *
93 * VolumeData's variant holds float and glm::vec3 only. A grid whose leaf
94 * value type is double, int32, int64, bool or half is narrowed to float; a
95 * vec3d or vec3i grid is narrowed to glm::vec3, via MayaFlux::try_convert
96 * per element (per component, for vectors). This is logged once per grid
97 * at MF_WARN, distinguishing a narrowing that round-tripped every element
98 * exactly from one where at least one element actually lost precision —
99 * the common case for a double or int64 grid whose range exceeds float,
100 * uncommon for a value meant to feed a float GPU field in the first place,
101 * which is the purpose this reader is built for. Accepted as the cost of a
102 * single representable type rather than widening VolumeData to carry every
103 * tinyvdb value type.
104 */
105class MAYAFLUX_API VolumeReader : public FileReader {
106public:
107 VolumeReader();
108 ~VolumeReader() override;
109
110 // -------------------------------------------------------------------------
111 // Primary API — use these
112 // -------------------------------------------------------------------------
113
114 /**
115 * @brief Load every selected grid from a file in one call.
116 *
117 * Opens, reads, extracts and closes in a single synchronous operation.
118 * The output lattice is the union of active bboxes; see the class docs.
119 *
120 * @param filepath Path to the .vdb file.
121 * @param options Grid selection.
122 * @return Populated VolumeData, or nullopt on failure; check
123 * get_last_error().
124 */
125 [[nodiscard]] std::optional<Kakshya::VolumeData> load(
126 const std::string& filepath, const VolumeReadOptions& options = {});
127
128 /**
129 * @brief Load every selected grid onto a caller-supplied lattice.
130 *
131 * As load(), but the output lattice is @p lattice rather than derived
132 * from the file. The only path that round-trips a write exactly; see
133 * the class docs.
134 *
135 * @param filepath Path to the .vdb file.
136 * @param lattice Output lattice. Its resolution and bounds are used
137 * as given; nothing about it is validated against the
138 * file's own transform.
139 * @param options Grid selection.
140 * @return Populated VolumeData, or nullopt on failure; check
141 * get_last_error().
142 */
143 [[nodiscard]] std::optional<Kakshya::VolumeData> load(
144 const std::string& filepath,
145 const Kinesis::Lattice3D& lattice,
146 const VolumeReadOptions& options = {});
147
148 /**
149 * @brief Extract every selected grid after open() has already been called.
150 *
151 * As load(), but reads the currently open file rather than opening one.
152 * Does not call close().
153 *
154 * @param options Grid selection.
155 * @return Populated VolumeData, or nullopt if no file is open or on
156 * failure; check get_last_error().
157 */
158 [[nodiscard]] std::optional<Kakshya::VolumeData> extract(
159 const VolumeReadOptions& options = {}) const;
160
161 /**
162 * @brief Extract every selected grid onto a caller-supplied lattice.
163 * @param lattice Output lattice, as in the load() overload.
164 * @param options Grid selection.
165 * @return Populated VolumeData, or nullopt if no file is open or on
166 * failure; check get_last_error().
167 */
168 [[nodiscard]] std::optional<Kakshya::VolumeData> extract(
169 const Kinesis::Lattice3D& lattice,
170 const VolumeReadOptions& options = {}) const;
171
172 // -------------------------------------------------------------------------
173 // FileReader interface
174 // -------------------------------------------------------------------------
175
176 [[nodiscard]] bool can_read(const std::string& filepath) const override;
177
178 bool open(const std::string& filepath,
179 FileReadOptions options = FileReadOptions::ALL) override;
180
181 void close() override;
182
183 [[nodiscard]] bool is_open() const override { return m_is_open; }
184
185 [[nodiscard]] std::optional<FileMetadata> get_metadata() const override;
186
187 [[nodiscard]] std::vector<FileRegion> get_regions() const override { return {}; }
188 std::vector<Kakshya::DataVariant> read_all() override { return {}; }
189 std::vector<Kakshya::DataVariant> read_region(const FileRegion& /*region*/) override { return {}; }
190
191 /**
192 * @brief No-op. Volume data does not use SignalSourceContainer.
193 * @return nullptr always.
194 */
195 std::shared_ptr<Kakshya::SignalSourceContainer> create_container() override;
196
197 /**
198 * @brief No-op. Volume data does not use SignalSourceContainer.
199 * @return false always.
200 */
201 bool load_into_container(
202 std::shared_ptr<Kakshya::SignalSourceContainer> container) override;
203
204 [[nodiscard]] std::vector<uint64_t> get_read_position() const override { return { 0 }; }
205 bool seek(const std::vector<uint64_t>& /*position*/) override { return true; }
206 [[nodiscard]] std::vector<std::string> get_supported_extensions() const override { return { ".vdb" }; }
207
208 [[nodiscard]] std::type_index get_data_type() const override
209 {
210 return typeid(std::vector<uint8_t>);
211 }
212
213 [[nodiscard]] std::type_index get_container_type() const override
214 {
215 return typeid(void);
216 }
217
218 [[nodiscard]] std::string get_last_error() const override { return m_last_error; }
219
220 [[nodiscard]] bool supports_streaming() const override { return false; }
221 [[nodiscard]] uint64_t get_preferred_chunk_size() const override { return 0; }
222 [[nodiscard]] size_t get_num_dimensions() const override { return 0; }
223 [[nodiscard]] std::vector<uint64_t> get_dimension_sizes() const override { return {}; }
224
225private:
226 std::unique_ptr<Detail::VDBArchive> m_archive;
227
228 bool m_is_open { false };
229 std::string m_filepath;
230 mutable std::string m_last_error;
231
232 /**
233 * @brief Build VolumeData from selected grids over an explicit region.
234 * @param indices Grid indices, in the order fields should appear.
235 * @param region_min Voxel-index origin, in the first grid's index space.
236 * @param lattice Output lattice. Its resolution drives every
237 * read_dense_scalar/read_dense_vector call.
238 * @return Populated VolumeData, or nullopt on failure.
239 */
240 [[nodiscard]] std::optional<Kakshya::VolumeData> materialize(
241 const std::vector<size_t>& indices,
242 const glm::ivec3& region_min,
243 const Kinesis::Lattice3D& lattice) const;
244
245 void set_error(std::string msg) const { m_last_error = std::move(msg); }
246};
247
248} // namespace MayaFlux::IO
glm::ivec3 region_min
Abstract interface for reading various file formats into containers.
bool is_open() const override
Check if a file is currently open.
std::vector< FileRegion > get_regions() const override
Get semantic regions from the file.
void set_error(std::string msg) const
std::vector< std::string > get_supported_extensions() const override
Get supported file extensions for this reader.
bool seek(const std::vector< uint64_t > &) override
Seek to a specific position in the file.
std::unique_ptr< Detail::VDBArchive > m_archive
bool supports_streaming() const override
Check if streaming is supported for the current file.
std::vector< Kakshya::DataVariant > read_region(const FileRegion &) override
Read a specific region of data.
size_t get_num_dimensions() const override
Get the dimensionality of the file data.
std::type_index get_data_type() const override
Get the data type this reader produces.
std::vector< uint64_t > get_read_position() const override
Get current read position in primary dimension.
uint64_t get_preferred_chunk_size() const override
Get the preferred chunk size for streaming.
std::string get_last_error() const override
Get the last error message.
std::type_index get_container_type() const override
Get the container type this reader creates.
std::vector< uint64_t > get_dimension_sizes() const override
Get size of each dimension in the file data.
std::vector< Kakshya::DataVariant > read_all() override
Read all data from the file into memory.
tinyvdb-backed loader for volumetric grid files.
FileReadOptions
Generic options for file reading behavior.
auto create_container(Args &&... args) -> std::shared_ptr< ContainerType >
creates a new container of the specified type
Definition Depot.hpp:58
Generic region descriptor for any file type.
std::vector< std::string > field_names
Grids to load, matched by name.
Configuration for volume reading.
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25