9#ifdef MAYAFLUX_PLATFORM_WINDOWS
10#ifndef WIN32_LEAN_AND_MEAN
11#define WIN32_LEAN_AND_MEAN
18#include <Alembic/Abc/All.h>
19#include <Alembic/AbcCoreOgawa/All.h>
20#include <Alembic/AbcGeom/All.h>
22#ifdef MAYAFLUX_PLATFORM_WINDOWS
32 using namespace Alembic::AbcGeom;
39 return kConstantScope;
45 return kConstantScope;
55 bool uses_curve_schema(PrimitiveTopology topology)
57 return topology == PrimitiveTopology::LINE_LIST
58 || topology == PrimitiveTopology::LINE_STRIP;
61 using GeomParamVariant = std::variant<
62 OFloatGeomParam, OV2fGeomParam, OV3fGeomParam, OC3fGeomParam, ON3fGeomParam, OUInt32GeomParam>;
66 std::unordered_map<std::string, GeomParamVariant>
geom_params;
71 std::unordered_map<std::string, GeomParamVariant>
geom_params;
93 std::unordered_map<std::string, GeomParamVariant>& params,
94 const OCompoundProperty& arb_params,
95 const SpatialAttribute& attr,
96 size_t expected_count)
98 const auto scope = to_alembic_scope(attr.scope);
99 auto it = params.find(attr.name);
101 if (
const auto* floats = std::get_if<std::vector<float>>(&attr.values)) {
102 if (floats->size() != expected_count) {
105 if (it == params.end()) {
106 it = params.emplace(attr.name, OFloatGeomParam(arb_params, attr.name,
false, scope, 1)).first;
108 auto* param = std::get_if<OFloatGeomParam>(&it->second);
112 param->set(OFloatGeomParam::Sample(FloatArraySample(floats->data(), floats->size()), scope));
115 if (
const auto* uvs = std::get_if<std::vector<glm::vec2>>(&attr.values)) {
116 if (uvs->size() != expected_count) {
119 if (it == params.end()) {
120 it = params.emplace(attr.name, OV2fGeomParam(arb_params, attr.name,
false, scope, 1)).first;
122 auto* param = std::get_if<OV2fGeomParam>(&it->second);
126 param->set(OV2fGeomParam::Sample(
127 V2fArraySample(
reinterpret_cast<const V2f*
>(uvs->data()), uvs->size()), scope));
130 if (
const auto* vecs = std::get_if<std::vector<glm::vec3>>(&attr.values)) {
131 if (vecs->size() != expected_count) {
135 if (it == params.end()) {
136 if (attr.name ==
"color") {
137 it = params.emplace(attr.name, OC3fGeomParam(arb_params, attr.name,
false, scope, 1)).first;
138 }
else if (attr.name ==
"normal") {
139 it = params.emplace(attr.name, ON3fGeomParam(arb_params, attr.name,
false, scope, 1)).first;
141 it = params.emplace(attr.name, OV3fGeomParam(arb_params, attr.name,
false, scope, 1)).first;
145 if (
auto* color_param = std::get_if<OC3fGeomParam>(&it->second)) {
146 color_param->set(OC3fGeomParam::Sample(
147 C3fArraySample(
reinterpret_cast<const C3f*
>(vecs->data()), vecs->size()), scope));
150 if (
auto* normal_param = std::get_if<ON3fGeomParam>(&it->second)) {
151 normal_param->set(ON3fGeomParam::Sample(
152 N3fArraySample(
reinterpret_cast<const N3f*
>(vecs->data()), vecs->size()), scope));
155 if (
auto* vector_param = std::get_if<OV3fGeomParam>(&it->second)) {
156 vector_param->set(OV3fGeomParam::Sample(
157 V3fArraySample(
reinterpret_cast<const V3f*
>(vecs->data()), vecs->size()), scope));
162 if (
const auto* uints = std::get_if<std::vector<uint32_t>>(&attr.values)) {
163 if (uints->size() != expected_count) {
166 if (it == params.end()) {
167 it = params.emplace(attr.name, OUInt32GeomParam(arb_params, attr.name,
false, scope, 1)).first;
169 auto* param = std::get_if<OUInt32GeomParam>(&it->second);
173 param->set(OUInt32GeomParam::Sample(UInt32ArraySample(uints->data(), uints->size()), scope));
179 size_t vertex_attribute_count(
SpatialScope scope,
size_t point_count)
186 size_t curves_attribute_count(
SpatialScope scope,
size_t vertex_count,
size_t curve_count)
216 : m_impl(
std::make_unique<
Impl>())
229 m_impl->archive.emplace(Alembic::AbcCoreOgawa::WriteArchive(), resolved);
230 }
catch (
const std::exception& e) {
231 set_error(std::string(
"open: ") + e.what());
233 "SpatialCache::open: failed for '{}': {}", resolved, e.what());
238 "SpatialCache: opened '{}'", resolved);
244 auto it =
m_impl->vertex_streams.find(stream_name);
245 if (it ==
m_impl->vertex_streams.end()) {
247 auto mit =
m_impl->pending_metadata.find(stream_name);
248 if (mit !=
m_impl->pending_metadata.end()) {
250 m_impl->pending_metadata.erase(mit);
252 auto stream = std::make_unique<VertexStream>();
253 stream->object = OPoints(
m_impl->archive->getTop(), stream_name, md);
254 it =
m_impl->vertex_streams.emplace(stream_name, std::move(
stream)).first;
256 auto&
stream = *it->second;
257 auto& schema =
stream.object.getSchema();
259 const P3fArraySample pos_sample(
261 const V3fArraySample vel_sample = sample.
velocities.empty()
263 : V3fArraySample(
reinterpret_cast<const V3f*
>(sample.
velocities.data()), sample.
velocities.size());
265 if (sample.
ids.empty()) {
266 schema.set(OPointsSchema::Sample(pos_sample, vel_sample));
268 const UInt64ArraySample id_sample(sample.
ids.data(), sample.
ids.size());
269 schema.set(OPointsSchema::Sample(pos_sample, id_sample, vel_sample));
273 const size_t expected = vertex_attribute_count(attr.scope, sample.
positions.size());
274 if (!set_attribute(
stream.geom_params, schema.getArbGeomParams(), attr, expected)) {
275 set_error(
"write: attribute '" + attr.name +
"' has an unsupported type, wrong "
276 +
"element count, or was redeclared with a different type");
288 const size_t expected_vertex_count = std::accumulate(
290 [](
size_t acc, int32_t n) { return acc + static_cast<size_t>(n); });
291 if (expected_vertex_count != sample.
positions.size()) {
292 set_error(
"write: vertex_counts_per_curve sum does not match positions.size()");
298 auto it =
m_impl->curves_streams.find(stream_name);
299 if (it ==
m_impl->curves_streams.end()) {
301 auto mit =
m_impl->pending_metadata.find(stream_name);
302 if (mit !=
m_impl->pending_metadata.end()) {
304 m_impl->pending_metadata.erase(mit);
306 auto stream = std::make_unique<CurvesStream>();
307 stream->object = OCurves(
m_impl->archive->getTop(), stream_name, md);
308 it =
m_impl->curves_streams.emplace(stream_name, std::move(
stream)).first;
310 auto&
stream = *it->second;
311 auto& schema =
stream.object.getSchema();
313 const P3fArraySample pos_sample(
315 const Int32ArraySample nverts_sample(
318 schema.set(OCurvesSchema::Sample(pos_sample, nverts_sample, kLinear, kNonPeriodic));
321 const size_t expected = curves_attribute_count(
323 if (!set_attribute(
stream.geom_params, schema.getArbGeomParams(), attr, expected)) {
324 set_error(
"write: attribute '" + attr.name +
"' has an unsupported type, wrong "
325 +
"element count, or was redeclared with a different type");
337 std::lock_guard lock(
m_impl->mutex);
348 if (uses_curve_schema(sample.
topology)) {
350 set_error(
"write: ids/velocities must be empty for a curve topology");
354 set_error(
"write: vertex_counts_per_curve is required for a curve topology");
360 if (sample.
topology == PrimitiveTopology::POINT_LIST) {
362 set_error(
"write: vertex_counts_per_curve must be empty for POINT_LIST");
368 set_error(
"write: unsupported topology; SpatialCache only writes "
369 "POINT_LIST, LINE_LIST, LINE_STRIP");
375 const std::string& stream_name,
376 const std::unordered_map<std::string, std::string>& tags)
378 std::lock_guard lock(
m_impl->mutex);
380 if (
m_impl->vertex_streams.contains(stream_name) ||
m_impl->curves_streams.contains(stream_name)) {
381 set_error(
"write_metadata: stream '" + stream_name
382 +
"' already created; call write_metadata before its first write() call");
387 auto& md =
m_impl->pending_metadata[stream_name];
388 for (
const auto& [key,
value] : tags) {
396 std::lock_guard lock(
m_impl->mutex);
402 m_impl->vertex_streams.clear();
403 m_impl->curves_streams.clear();
404 m_impl->pending_metadata.clear();
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
Core::GlobalStreamInfo stream
std::unordered_map< std::string, GeomParamVariant > geom_params
std::unique_ptr< Impl > m_impl
bool write_metadata(const std::string &stream_name, const std::unordered_map< std::string, std::string > &tags)
Queue object-level metadata tags for a named stream.
bool write_curves_sample(const std::string &stream_name, const SpatialSample &sample)
bool write_vertex_sample(const std::string &stream_name, const SpatialSample &sample)
std::string get_last_error() const
void set_error(std::string msg) const
bool write(const std::string &stream_name, const SpatialSample &sample)
Append one sample to a named stream.
void close()
Finalize and close the archive.
Alembic-backed writer for time-sampled spatial entity state: particle systems, point clouds,...
SpatialScope
How many elements one attribute contributes per sample.
std::string resolve_write_path(const std::string &filepath)
Anchor a relative output path to Config::SOURCE_DIR.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
PrimitiveTopology
Vertex assembly primitive topology.
std::unordered_map< std::string, MetaData > pending_metadata
std::unordered_map< std::string, std::unique_ptr< CurvesStream > > curves_streams
std::mutex mutex
Guards the members above: write() and close() can run on different threads.
std::optional< OArchive > archive
std::unordered_map< std::string, std::unique_ptr< VertexStream > > vertex_streams
std::span< const glm::vec3 > positions
std::span< const glm::vec3 > velocities
std::span< const SpatialAttribute > attributes
Portal::Graphics::PrimitiveTopology topology
std::span< const int32_t > vertex_counts_per_curve
std::span< const uint64_t > ids
One sample's worth of data for one named stream.