11#ifdef MAYAFLUX_PLATFORM_WINDOWS
15#include <Alembic/Abc/All.h>
16#include <Alembic/AbcCoreFactory/All.h>
17#include <Alembic/AbcCoreOgawa/All.h>
18#include <Alembic/AbcGeom/All.h>
20#ifdef MAYAFLUX_PLATFORM_WINDOWS
30 using namespace Alembic::AbcGeom;
33 enum class AttributeKind : uint8_t {
42 struct AttributeDecl {
47 struct SampleStorage {
50 std::vector<uint64_t>
ids;
56 std::variant<IPoints, ICurves>
object;
57 PrimitiveTopology
topology { PrimitiveTopology::POINT_LIST };
58 std::unordered_map<std::string, std::string>
metadata;
66 std::optional<SpatialScope> from_alembic_scope(GeometryScope scope)
81 std::optional<AttributeKind> classify_attribute(
82 const Alembic::AbcCoreAbstract::PropertyHeader& header)
84 if (IFloatGeomParam::matches(header)) {
85 return AttributeKind::Float;
87 if (IV2fGeomParam::matches(header)) {
88 return AttributeKind::Vec2;
90 if (IC3fGeomParam::matches(header)) {
91 return AttributeKind::Color;
93 if (IN3fGeomParam::matches(header)) {
94 return AttributeKind::Normal;
96 if (IV3fGeomParam::matches(header)) {
97 return AttributeKind::Vec3;
99 if (IUInt32GeomParam::matches(header)) {
100 return AttributeKind::UInt32;
105 template <
class Elem,
class Src>
106 std::vector<Elem> copy_elements(
const Src*
src,
size_t count)
108 static_assert(
sizeof(Elem) ==
sizeof(Src));
109 const auto* typed =
reinterpret_cast<const Elem*
>(
src);
110 return std::vector<Elem>(typed, typed +
count);
113 template <
class Elem,
class ArrayPtr>
114 std::vector<Elem> copy_array(
const ArrayPtr& array)
119 return copy_elements<Elem>(array->get(), array->size());
122 template <
class Param,
class Elem>
123 std::optional<SpatialAttribute> read_param(
124 const ICompoundProperty& arb,
125 const AttributeDecl& decl,
126 const ISampleSelector& selector)
128 const Param param(arb, decl.name);
129 const auto sample = param.getExpandedValue(selector);
130 const auto values = sample.getVals();
131 const auto scope = from_alembic_scope(sample.getScope());
136 return SpatialAttribute {
143 std::optional<SpatialAttribute> read_attribute(
144 const ICompoundProperty& arb,
145 const AttributeDecl& decl,
146 const ISampleSelector& selector)
149 case AttributeKind::Float:
150 return read_param<IFloatGeomParam, float>(arb, decl, selector);
151 case AttributeKind::Vec2:
152 return read_param<IV2fGeomParam, glm::vec2>(arb, decl, selector);
153 case AttributeKind::Vec3:
154 return read_param<IV3fGeomParam, glm::vec3>(arb, decl, selector);
155 case AttributeKind::Color:
156 return read_param<IC3fGeomParam, glm::vec3>(arb, decl, selector);
157 case AttributeKind::Normal:
158 return read_param<IN3fGeomParam, glm::vec3>(arb, decl, selector);
159 case AttributeKind::UInt32:
160 return read_param<IUInt32GeomParam, uint32_t>(arb, decl, selector);
165 std::vector<AttributeDecl> declare_attributes(
166 const ICompoundProperty& arb,
const std::string& stream_name)
168 std::vector<AttributeDecl> decls;
173 for (
size_t i = 0; i < arb.getNumProperties(); ++i) {
174 const auto& header = arb.getPropertyHeader(i);
175 const auto kind = classify_attribute(header);
178 "SpatialReader: '{}': attribute '{}' has a type this reader does not read, skipped",
179 stream_name, header.getName());
182 decls.push_back({ .name = header.getName(), .
kind = *
kind });
188 enum class ArchiveFormat : uint8_t {
205 std::optional<ArchiveFormat> sniff_format(
const std::string& path)
207 constexpr std::string_view k_ogawa {
"Ogawa" };
208 constexpr std::string_view k_hdf5 {
"\x89HDF\r\n\x1a\n", 8 };
210 std::ifstream file(path, std::ios::binary);
215 std::array<char, 8> head {};
216 file.read(head.data(),
static_cast<std::streamsize
>(head.size()));
217 const auto count =
static_cast<size_t>(file.gcount());
219 if (
count >= k_ogawa.size() && std::string_view(head.data(), k_ogawa.size()) == k_ogawa) {
220 return ArchiveFormat::Ogawa;
222 if (
count >= k_hdf5.size() && std::string_view(head.data(), k_hdf5.size()) == k_hdf5) {
223 return ArchiveFormat::HDF5;
228 std::unordered_map<std::string, std::string> user_metadata(
const MetaData& md)
230 std::unordered_map<std::string, std::string> tags;
231 for (
const auto& [key,
value] : md) {
232 if (key ==
"schema" || key ==
"schemaObjTitle" || key ==
"schemaBaseType") {
235 tags.emplace(key,
value);
245 std::unordered_map<std::string, StreamRecord>
records;
255 const IObject top =
archive.getTop();
257 for (
size_t i = 0; i < top.getNumChildren(); ++i) {
258 const auto& header = top.getChildHeader(i);
259 const std::string&
name = header.getName();
262 record.metadata = user_metadata(header.getMetaData());
264 ICompoundProperty arb;
266 if (IPoints::matches(header)) {
268 auto& schema =
points.getSchema();
269 record.topology = PrimitiveTopology::POINT_LIST;
270 record.sample_count = schema.getNumSamples();
271 record.time_sampling = schema.getTimeSampling();
272 arb = schema.getArbGeomParams();
273 record.object = std::move(
points);
274 }
else if (ICurves::matches(header)) {
275 ICurves curves(top,
name);
276 auto& schema = curves.getSchema();
277 record.topology = PrimitiveTopology::LINE_STRIP;
278 record.sample_count = schema.getNumSamples();
279 record.time_sampling = schema.getTimeSampling();
280 arb = schema.getArbGeomParams();
281 record.object = std::move(curves);
284 "SpatialReader: object '{}' is neither points nor curves, skipped",
name);
288 record.attributes = declare_attributes(arb,
name);
311 if (!std::filesystem::is_regular_file(resolved, ec)) {
312 set_error(
"open: no such file: " + resolved);
314 "SpatialReader::open: '{}' does not exist", resolved);
318 const auto format = sniff_format(resolved);
320 set_error(
"open: not an Alembic archive: " + resolved);
322 "SpatialReader::open: '{}' is not an Alembic archive", resolved);
327 IArchive archive = *
format == ArchiveFormat::Ogawa
328 ? IArchive(Alembic::AbcCoreOgawa::ReadArchive(), resolved)
329 : Alembic::AbcCoreFactory::IFactory().getArchive(resolved);
330 if (!archive.valid()) {
331 set_error(
"open: archive could not be read: " + resolved);
333 "SpatialReader::open: '{}' could not be read as an archive", resolved);
337 m_impl->archive = archive;
338 m_impl->index_streams();
339 }
catch (
const std::exception& e) {
341 set_error(std::string(
"open: ") + e.what());
343 "SpatialReader::open: failed for '{}': {}", resolved, e.what());
348 "SpatialReader: opened '{}', {} stream(s)", resolved, m_impl->names.size());
356 m_impl->archive = IArchive();
361 return m_impl->archive.valid();
371 const auto it =
m_impl->records.find(stream_name);
372 if (it ==
m_impl->records.end()) {
375 return it->second.topology;
380 const auto it =
m_impl->records.find(stream_name);
381 return it ==
m_impl->records.end() ? 0 : it->second.sample_count;
385 const std::string& stream_name)
const
387 const auto it =
m_impl->records.find(stream_name);
388 if (it ==
m_impl->records.end()) {
391 return it->second.metadata;
395 const std::string& stream_name,
size_t index)
const
397 if (!
m_impl->archive.valid()) {
398 set_error(
"read_sample: no archive open");
402 const auto it =
m_impl->records.find(stream_name);
403 if (it ==
m_impl->records.end()) {
404 set_error(
"read_sample: no stream named '" + stream_name +
"'");
408 auto& record = it->second;
409 if (
index >= record.sample_count) {
410 set_error(
"read_sample: index " + std::to_string(
index) +
" out of range for '"
411 + stream_name +
"' (" + std::to_string(record.sample_count) +
" samples)");
416 const ISampleSelector selector(
static_cast<index_t
>(
index));
418 ICompoundProperty arb;
420 if (
auto*
points = std::get_if<IPoints>(&record.object)) {
421 auto& schema =
points->getSchema();
422 IPointsSchema::Sample sample;
423 schema.get(sample, selector);
425 fresh.positions = copy_array<glm::vec3>(sample.getPositions());
426 fresh.ids = copy_array<uint64_t>(sample.getIds());
427 fresh.velocities = copy_array<glm::vec3>(sample.getVelocities());
428 arb = schema.getArbGeomParams();
429 }
else if (
auto* curves = std::get_if<ICurves>(&record.object)) {
430 auto& schema = curves->getSchema();
431 ICurvesSchema::Sample sample;
432 schema.get(sample, selector);
434 fresh.positions = copy_array<glm::vec3>(sample.getPositions());
435 fresh.vertex_counts_per_curve = copy_array<int32_t>(sample.getCurvesNumVertices());
436 arb = schema.getArbGeomParams();
438 if (!record.warned_curve_type
439 && (sample.getType() != kLinear || sample.getWrap() != kNonPeriodic)) {
440 record.warned_curve_type =
true;
442 "SpatialReader: '{}' is not linear and non periodic, its control points are "
443 "returned as plain polylines",
449 for (
const auto& decl : record.attributes) {
450 auto attribute = read_attribute(arb, decl, selector);
453 "SpatialReader: '{}' sample {}: attribute '{}' has an unsupported scope or "
454 "no values, skipped",
455 stream_name,
index, decl.name);
458 fresh.attributes.push_back(std::move(*attribute));
462 record.storage = std::move(fresh);
465 .positions = record.storage.positions,
466 .vertex_counts_per_curve = record.storage.vertex_counts_per_curve,
467 .ids = record.storage.ids,
468 .velocities = record.storage.velocities,
469 .attributes = record.storage.attributes,
471 }
catch (
const std::exception& e) {
472 set_error(std::string(
"read_sample: ") + e.what());
474 "SpatialReader::read_sample: '{}' sample {}: {}", stream_name,
index, e.what());
480 const std::string& stream_name,
size_t index)
const
482 const auto it =
m_impl->records.find(stream_name);
483 if (it ==
m_impl->records.end()
484 ||
index >= it->second.sample_count
485 || !it->second.time_sampling) {
489 return static_cast<double>(
490 it->second.time_sampling->getSampleTime(
static_cast<index_t
>(
index)));
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
std::vector< glm::vec2 > * points
std::vector< int32_t > vertex_counts_per_curve
std::vector< uint64_t > ids
std::unordered_map< std::string, std::string > metadata
std::vector< glm::vec3 > positions
std::vector< SpatialAttribute > attributes
PrimitiveTopology topology
Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling
std::vector< glm::vec3 > velocities
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.
std::optional< SpatialSample > read_sample(const std::string &stream_name, size_t index) const
Read one sample of a stream.
size_t get_sample_count(const std::string &stream_name) const
Number of samples in a stream, 0 for an unknown stream.
void set_error(std::string msg) const
void close()
Release the archive.
std::unique_ptr< Impl > m_impl
std::optional< Portal::Graphics::PrimitiveTopology > get_topology(const std::string &stream_name) const
Topology a stream is read as: POINT_LIST or LINE_STRIP.
const std::vector< std::string > & get_stream_names() const
Names of the streams found at open(), in file order.
std::unordered_map< std::string, std::string > get_metadata(const std::string &stream_name) const
Object level tags a stream was written with, the same form SpatialCache::write_metadata takes.
std::optional< double > sample_time(const std::string &stream_name, size_t index) const
Time in seconds the archive assigns to a sample.
Reads back the time-sampled streams a SpatialCache wrote, in the same SpatialSample format it writes.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
PrimitiveTopology
Vertex assembly primitive topology.
std::unordered_map< std::string, StreamRecord > records
std::vector< std::string > names
Portal::Graphics::PrimitiveTopology topology
One sample's worth of data for one named stream.