MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SpatialCache.hpp
Go to the documentation of this file.
1#pragma once
2
4
6enum class PrimitiveTopology : uint8_t;
7}
8
9namespace MayaFlux::IO {
10
11/**
12 * @enum SpatialScope
13 * @brief How many elements one attribute contributes per sample.
14 *
15 * Mirrors Alembic's own GeometryScope, narrowed to the three values that
16 * apply to points and curves (kVertexScope/kFacevaryingScope are mesh-only
17 * concepts with no equivalent here):
18 * Constant: one value for the whole stream.
19 * Uniform: one value per curve. Meaningless for PrimitiveTopology::POINT_LIST
20 * (a point cloud has no sub-grouping smaller than the stream
21 * itself, so it is treated as Constant there).
22 * Varying: one value per point, or per curve vertex for curve topologies.
23 */
24enum class SpatialScope : uint8_t {
26 Uniform,
27 Varying,
28};
29
30/**
31 * @struct SpatialAttribute
32 * @brief One named, scoped channel of already-typed values.
33 *
34 * values holds one of Kakshya::DataVariant's float/glm::vec2/glm::vec3/
35 * uint32_t vectors, sized per SpatialScope against the sample it is
36 * attached to (1 for Constant, curve count for Uniform, point/vertex count
37 * for Varying). Any other DataVariant alternative is rejected: those are
38 * the only four with a native Alembic GeomParam counterpart
39 * (OFloatGeomParam/OV2fGeomParam/OV3fGeomParam/OUInt32GeomParam).
40 *
41 * A vec3 named exactly "color" or "normal" is declared through Alembic's
42 * dedicated color/normal GeomParam types instead of a plain vector one, so
43 * an importer recognizes it as such rather than an arbitrary direction;
44 * every other name, vec3 or otherwise, stays generic. MayaFlux's own
45 * vertex fields already use these two names for these two roles, so this
46 * costs a caller nothing to opt into.
47 */
53
54/**
55 * @struct SpatialSample
56 * @brief One sample's worth of data for one named stream.
57 *
58 * topology decides which of the remaining fields apply:
59 * POINT_LIST: ids/velocities meaningful, vertex_counts_per_curve must be
60 * empty (every position is its own point).
61 * LINE_LIST / LINE_STRIP: vertex_counts_per_curve required (its sum must
62 * equal positions.size()), ids/velocities must be empty
63 * (Alembic's Curves schema has neither).
64 * Any other topology is rejected: triangle data belongs to ModelWriter, not
65 * this class.
66 */
69 std::span<const glm::vec3> positions;
70 std::span<const int32_t> vertex_counts_per_curve;
71 std::span<const uint64_t> ids;
72 std::span<const glm::vec3> velocities;
73 std::span<const SpatialAttribute> attributes;
74};
75
76/**
77 * @class SpatialCache
78 * @brief Alembic-backed writer for time-sampled spatial entity state:
79 * particle systems, point clouds, inferred topology, and any other
80 * positions-plus-named-attributes-over-time source.
81 *
82 * Named after what it does, not the backend library or one data shape:
83 * this is a cache of spatial state over time, not "the Alembic class."
84 * Alembic is an implementation detail hidden behind Impl, the same
85 * convention ModelReader uses to keep Assimp headers out of its own
86 * public interface.
87 *
88 * Deliberately not registry-dispatched like ModelWriter/VolumeWriter: those
89 * registries exist because several backend libraries could plausibly
90 * implement the same format-agnostic contract. No second library writes
91 * .abc files, so a registry here would be indirection with nothing to
92 * dispatch between.
93 *
94 * Each named stream keeps its own Alembic sample count: calling write()
95 * for a given stream_name appends one sample to that stream's own
96 * timeline. There is no separate "advance time" step; Alembic's own
97 * OTypedSchema::set() is what appends, and calling it once per capture
98 * tick per stream already produces the sequence. Streams written at
99 * different cadences (e.g. particles every frame, bonds only when they
100 * change) simply end up with different sample counts on their own
101 * timelines, which Alembic permits.
102 *
103 * Object-level metadata (write_metadata) is Alembic MetaData, fixed at the
104 * point a stream's underlying object is first created and not
105 * retroactively settable by Alembic's own API. Call write_metadata for a
106 * stream_name before its first write() call for the tags to take effect;
107 * calling it after is logged and ignored.
108 *
109 * Usage:
110 * @code
111 * IO::SpatialCache cache;
112 * cache.open("swarm.abc");
113 *
114 * cache.write_metadata("particles", { { "operator", "PhysicsOperator" } });
115 * cache.write("particles", {
116 * .topology = Portal::Graphics::PrimitiveTopology::POINT_LIST,
117 * .positions = positions,
118 * .ids = ids,
119 * .attributes = attributes,
120 * });
121 * // ... one write() call per capture tick ...
122 *
123 * cache.close();
124 * @endcode
125 */
126class MAYAFLUX_API SpatialCache {
127public:
128 SpatialCache();
130
131 SpatialCache(const SpatialCache&) = delete;
134 SpatialCache& operator=(SpatialCache&&) noexcept;
135
136 /**
137 * @brief Open an Alembic archive for writing, Ogawa backend.
138 * @param filepath Destination path. Anchored via resolve_write_path.
139 * @return True on success. On failure call get_last_error().
140 */
141 bool open(const std::string& filepath);
142
143 /**
144 * @brief Append one sample to a named stream.
145 *
146 * Creates the stream's underlying Alembic object on first call for
147 * this stream_name (applying any tags queued by write_metadata) and
148 * declares each attribute's OGeomParam at that point. Subsequent calls
149 * for the same stream_name must keep passing the same topology and the
150 * same attributes (same names, types, scopes, same order): declaration
151 * is one-time, only values change per sample.
152 *
153 * @param stream_name Name this stream is written under.
154 * @param sample This sample's data. See SpatialSample's own doc
155 * for which fields apply to which topology.
156 * @return True on success. On failure call get_last_error().
157 */
158 bool write(const std::string& stream_name, const SpatialSample& sample);
159
160 /**
161 * @brief Queue object-level metadata tags for a named stream.
162 *
163 * Must be called before that stream_name's first write() call: Alembic
164 * MetaData is fixed at object creation and cannot be changed
165 * afterward. A call arriving after the stream's object already exists
166 * is logged and has no effect.
167 *
168 * @param stream_name Stream these tags apply to.
169 * @param tags Key-value pairs, e.g. run parameters
170 * (gravity, bounds, rule shader name).
171 * @return True if queued. False only if the stream's object was
172 * already created before this call.
173 */
174 bool write_metadata(
175 const std::string& stream_name,
176 const std::unordered_map<std::string, std::string>& tags);
177
178 /**
179 * @brief Finalize and close the archive.
180 *
181 * Safe to call on an unopened or already-closed cache; both are no-ops.
182 */
183 void close();
184
185 [[nodiscard]] std::string get_last_error() const { return m_last_error; }
186
187private:
188 struct Impl;
189 std::unique_ptr<Impl> m_impl;
190
191 mutable std::string m_last_error;
192
193 void set_error(std::string msg) const { m_last_error = std::move(msg); }
194
195 bool write_vertex_sample(const std::string& stream_name, const SpatialSample& sample);
196 bool write_curves_sample(const std::string& stream_name, const SpatialSample& sample);
197};
198
199} // namespace MayaFlux::IO
std::unique_ptr< Impl > m_impl
SpatialCache(SpatialCache &&) noexcept
SpatialCache & operator=(const SpatialCache &)=delete
void set_error(std::string msg) const
SpatialCache(const SpatialCache &)=delete
Alembic-backed writer for time-sampled spatial entity state: particle systems, point clouds,...
SpatialScope
How many elements one attribute contributes per sample.
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.
Definition NDData.hpp:102
PrimitiveTopology
Vertex assembly primitive topology.
Kakshya::DataVariant values
One named, scoped channel of already-typed values.
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.