11 std::string extension_of(
const std::string& filepath)
13 auto ext = std::filesystem::path(filepath).extension().string();
14 if (!ext.empty() && ext[0] ==
'.') {
17 std::ranges::transform(ext, ext.begin(),
18 [](
unsigned char c) { return std::tolower(c); });
30 std::vector<size_t> select_indices(
31 const Detail::VDBArchive& archive,
33 const std::vector<std::string>& field_names)
35 std::vector<size_t> indices;
37 if (field_names.empty()) {
38 indices.reserve(
count);
39 for (
size_t i = 0; i <
count; ++i) {
45 indices.reserve(field_names.size());
46 for (
const auto&
name : field_names) {
48 for (
size_t i = 0; i <
count; ++i) {
49 if (archive.grid_summary(i).name ==
name) {
57 "VolumeReader: no grid named '{}', skipped",
name);
69 if (token ==
"level set") {
72 if (token ==
"fog volume") {
75 if (token ==
"staggered") {
87 if (token ==
"covariant") {
90 if (token ==
"covariant normalize") {
91 return V::CovariantNormalize;
93 if (token ==
"contravariant relative") {
94 return V::ContravariantRelative;
96 if (token ==
"contravariant absolute") {
97 return V::ContravariantAbsolute;
109 : m_archive(
std::make_unique<Detail::VDBArchive>())
125 if (!
open(filepath)) {
128 auto result =
extract(options);
134 const std::string& filepath,
138 if (!
open(filepath)) {
141 auto result =
extract(lattice, options);
161 if (indices.empty()) {
166 glm::ivec3 union_min { 0 };
167 glm::ivec3 union_max { 0 };
168 bool have_any =
false;
170 for (
size_t idx : indices) {
171 const auto summary =
m_archive->grid_summary(idx);
172 if (!summary.has_active) {
176 union_min = summary.active_min;
177 union_max = summary.active_max;
180 union_min = glm::min(union_min, summary.active_min);
181 union_max = glm::max(union_max, summary.active_max);
186 set_error(
"Selected grids have no active voxels");
190 const auto first =
m_archive->grid_summary(indices.front());
193 lattice.
resolution = glm::uvec3(union_max - union_min);
194 lattice.
bounds.
min = first.translation + first.voxel_size * (glm::vec3(union_min) - 0.5F);
195 lattice.
bounds.
max = first.translation + first.voxel_size * (glm::vec3(union_max) - 0.5F);
215 if (indices.empty()) {
220 const auto first =
m_archive->grid_summary(indices.front());
221 if (glm::any(glm::lessThanEqual(first.voxel_size, glm::vec3(0.0F)))) {
222 set_error(
"First selected grid has a degenerate voxel size");
226 const glm::vec3 region_min_f = (lattice.
bounds.
min - first.translation) / first.voxel_size + glm::vec3(0.5F);
227 const glm::ivec3
region_min = glm::ivec3(glm::round(region_min_f));
233 const std::vector<size_t>& indices,
237 constexpr float k_transform_epsilon = 1e-5F;
239 const auto reference =
m_archive->grid_summary(indices.front());
240 for (
size_t idx : indices) {
241 if (idx == indices.front()) {
244 const auto summary =
m_archive->grid_summary(idx);
245 const bool mismatched = glm::any(glm::greaterThan(
246 glm::abs(summary.voxel_size - reference.voxel_size), glm::vec3(k_transform_epsilon)))
247 || glm::any(glm::greaterThan(
248 glm::abs(summary.translation - reference.translation), glm::vec3(k_transform_epsilon)));
251 "VolumeReader: grid '{}' has a different voxel size or translation than '{}', "
252 "its voxel indices are read directly against the shared region and will not be aligned",
253 summary.name, reference.name);
259 result.
fields.reserve(indices.size());
261 for (
size_t idx : indices) {
262 const auto summary =
m_archive->grid_summary(idx);
265 field.
name = summary.name;
270 if (summary.is_vector) {
273 std::vector<glm::vec3>
values;
281 std::vector<float>
values;
292 "VolumeReader: grid '{}' is not float/vec3f, narrowing to {} lost precision in at least one value",
293 summary.name, summary.is_vector ?
"vec3" :
"float");
294 }
else if (summary.narrowed) {
296 "VolumeReader: grid '{}' is not float/vec3f, narrowing to {} losslessly",
297 summary.name, summary.is_vector ?
"vec3" :
"float");
303 result.
fields.push_back(std::move(field));
306 if (result.
fields.empty()) {
312 set_error(
"Resulting VolumeData failed is_consistent()");
325 return extension_of(filepath) ==
"vdb";
333 set_error(
"Unsupported volume format: " + filepath);
344 "VolumeReader: open failed for '{}' — {}", filepath,
m_last_error);
352 "VolumeReader: opened '{}' — {} grid(s)",
353 std::filesystem::path(resolved).filename().
string(),
362 m_archive = std::make_unique<Detail::VDBArchive>();
383 m_last_error =
"Volume data does not use SignalSourceContainer. Use load() instead.";
388 std::shared_ptr<Kakshya::SignalSourceContainer> )
390 m_last_error =
"Volume data does not use SignalSourceContainer. Use load() instead.";
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< std::byte > values
static std::string resolve_path(const std::string &filepath)
Resolve a filepath against the project source root if not found as-is.
bool load_into_container(std::shared_ptr< Kakshya::SignalSourceContainer > container) override
No-op.
void set_error(std::string msg) const
std::optional< Kakshya::VolumeData > materialize(const std::vector< size_t > &indices, const glm::ivec3 ®ion_min, const Kinesis::Lattice3D &lattice) const
Build VolumeData from selected grids over an explicit region.
std::unique_ptr< Detail::VDBArchive > m_archive
bool can_read(const std::string &filepath) const override
Check if a file can be read by this reader.
bool open(const std::string &filepath, FileReadOptions options=FileReadOptions::ALL) override
Open a file for reading.
std::optional< FileMetadata > get_metadata() const override
Get metadata from the open file.
std::optional< Kakshya::VolumeData > load(const std::string &filepath, const VolumeReadOptions &options={})
Load every selected grid from a file in one call.
void close() override
Close the currently open file.
std::optional< Kakshya::VolumeData > extract(const VolumeReadOptions &options={}) const
Extract every selected grid after open() has already been called.
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
No-op.
FileReadOptions
Generic options for file reading behavior.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
LatticeValueClass
What the values sampled over a lattice mean geometrically.
VectorVariance
How a vector quantity's components behave under a change of frame.
std::vector< std::string > field_names
Grids to load, matched by name.
Configuration for volume reading.
Kinesis::Lattice3D lattice
bool is_consistent() const
Check that every field is densely populated over the lattice.
std::vector< VolumeField > fields
A lattice and every field sampled over it, held in host memory.
Kinesis::LatticeSemantics semantics
size_t element_count() const
Number of cells represented, dispatched on variant.
One named quantity sampled over a lattice, held in host memory.
AABB3D bounds
Continuous extent subdivided.
glm::uvec3 resolution
Cell count per axis. Zero on any axis is invalid.
A regular subdivision of an AABB3D into a cell count per axis.
VectorVariance variance
Vector quantities only.
LatticeValueClass value_class
Geometric meaning of the values.