MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ encode() [1/2]

bool MayaFlux::Nexus::StateEncoder::encode ( const Fabric fabric,
const std::string &  base_path 
)

Encode the given Fabric to {base_path}.exr and {base_path}.json.

Parameters
fabricSource of entity state. Only Emitters with a position are encoded in v0.
base_pathPath stem without extension.
Returns
True on success. On failure call last_error().

Definition at line 91 of file Encoder.cpp.

92{
93 m_last_error.clear();
94
95 // -------------------------------------------------------------------------
96 // Collect encodable entities.
97 // -------------------------------------------------------------------------
98 struct InternalRecord {
99 uint32_t id;
100 Fabric::Kind kind;
101 glm::vec3 position {};
102 float intensity { 0.0F };
103 float radius { 0.0F };
104 float query_radius { 0.0F };
105 std::optional<glm::vec3> color;
106 std::optional<float> size;
107 std::string influence_fn_name;
108 std::string perception_fn_name;
109 // Layer 0
110 float entity_type_norm { 0.0F };
111 float trigger_kind { 0.0F };
112 float time_kind { 0.0F };
113 // Layer 2
114 uint32_t sink_type { 0 };
115 uint32_t first_audio_channel { 0 };
116 };
117
118 std::vector<InternalRecord> records;
119
120 for (uint32_t id : fabric.all_ids()) {
121 const auto k = fabric.kind(id);
122 switch (k) {
124 auto e = fabric.get_emitter(id);
125 if (!e || !e->position()) {
126 continue;
127 }
128 if (e->fn_name().empty()) {
130 "StateEncoder: Emitter {} has no fn_name", id);
131 }
132 auto& rec = records.emplace_back(InternalRecord {
133 .id = id,
134 .kind = k,
135 .position = *e->position(),
136 .intensity = e->intensity(),
137 .radius = e->radius(),
138 .color = e->color(),
139 .size = e->size(),
140 .influence_fn_name = e->fn_name(),
141 .entity_type_norm = 0.0F,
142 });
143 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
144 rec.sink_type = (e->audio_sinks().empty() ? 0U : 1U)
145 | (e->render_sinks().empty() ? 0U : 2U);
146 if (!e->audio_sinks().empty())
147 rec.first_audio_channel = e->audio_sinks().front().channel;
148 break;
149 }
151 auto s = fabric.get_sensor(id);
152 if (!s || !s->position()) {
153 continue;
154 }
155 if (s->fn_name().empty()) {
157 "StateEncoder: Sensor {} has no fn_name", id);
158 }
159 auto& rec = records.emplace_back(InternalRecord {
160 .id = id,
161 .kind = k,
162 .position = *s->position(),
163 .query_radius = s->query_radius(),
164 .perception_fn_name = s->fn_name(),
165 .entity_type_norm = 0.333F,
166 });
167 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
168 break;
169 }
170 case Fabric::Kind::Agent: {
171 auto a = fabric.get_agent(id);
172 if (!a || !a->position()) {
173 continue;
174 }
175 if (a->perception_fn_name().empty()) {
177 "StateEncoder: Agent {} has no perception_fn_name", id);
178 }
179 if (a->influence_fn_name().empty()) {
181 "StateEncoder: Agent {} has no influence_fn_name", id);
182 }
183 auto& rec = records.emplace_back(InternalRecord {
184 .id = id,
185 .kind = k,
186 .position = *a->position(),
187 .intensity = a->intensity(),
188 .radius = a->radius(),
189 .query_radius = a->query_radius(),
190 .color = a->color(),
191 .size = a->size(),
192 .influence_fn_name = a->influence_fn_name(),
193 .perception_fn_name = a->perception_fn_name(),
194 .entity_type_norm = 0.667F,
195 });
196 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
197 rec.sink_type = (a->audio_sinks().empty() ? 0U : 1U)
198 | (a->render_sinks().empty() ? 0U : 2U);
199 if (!a->audio_sinks().empty())
200 rec.first_audio_channel = a->audio_sinks().front().channel;
201 break;
202 }
203 }
204 }
205
206 if (records.empty()) {
207 m_last_error = "No entities with positions to encode";
209 return false;
210 }
211
212 // -------------------------------------------------------------------------
213 // Compute per-field ranges.
214 // -------------------------------------------------------------------------
215 State::RangeSet rs;
216 bool init_pos_x = false, init_pos_y = false, init_pos_z = false;
217 bool init_intensity = false, init_radius = false, init_query_radius = false;
218 bool init_color_r = false, init_color_g = false, init_color_b = false;
219 bool init_size = false;
220
221 for (const auto& rec : records) {
222 expand_range(rs.pos_x, rec.position.x, init_pos_x);
223 expand_range(rs.pos_y, rec.position.y, init_pos_y);
224 expand_range(rs.pos_z, rec.position.z, init_pos_z);
225
226 if (rec.kind == Fabric::Kind::Emitter || rec.kind == Fabric::Kind::Agent) {
227 expand_range(rs.intensity, rec.intensity, init_intensity);
228 expand_range(rs.radius, rec.radius, init_radius);
229 if (rec.color) {
230 expand_range(rs.color_r, rec.color->r, init_color_r);
231 expand_range(rs.color_g, rec.color->g, init_color_g);
232 expand_range(rs.color_b, rec.color->b, init_color_b);
233 }
234 if (rec.size) {
235 expand_range(rs.size, *rec.size, init_size);
236 }
237 }
238 if (rec.kind == Fabric::Kind::Sensor || rec.kind == Fabric::Kind::Agent) {
239 expand_range(rs.query_radius, rec.query_radius, init_query_radius);
240 }
241 }
242
243 // -------------------------------------------------------------------------
244 // Build RGBA32F pixel buffer.
245 // -------------------------------------------------------------------------
246 const auto width = static_cast<uint32_t>(records.size());
247
248 IO::ImageData image;
249 image.width = width;
250 image.height = State::k_exr_rows;
251 image.channels = State::k_channels;
253
254 std::vector<float> pixels(static_cast<size_t>(width) * State::k_exr_rows * State::k_channels, 0.0F);
255
256 for (size_t i = 0; i < records.size(); ++i) {
257 const auto& rec = records[i];
258
259 const size_t row0 = (static_cast<size_t>(0) * width + i) * State::k_channels;
260 pixels[row0 + 0] = normalize(rec.position.x, rs.pos_x);
261 pixels[row0 + 1] = normalize(rec.position.y, rs.pos_y);
262 pixels[row0 + 2] = normalize(rec.position.z, rs.pos_z);
263 pixels[row0 + 3] = normalize(rec.intensity, rs.intensity);
264
265 const size_t row1 = (static_cast<size_t>(1) * width + i) * State::k_channels;
266 if (rec.color) {
267 pixels[row1 + 0] = normalize(rec.color->r, rs.color_r);
268 pixels[row1 + 1] = normalize(rec.color->g, rs.color_g);
269 pixels[row1 + 2] = normalize(rec.color->b, rs.color_b);
270 }
271 if (rec.size) {
272 pixels[row1 + 3] = normalize(*rec.size, rs.size);
273 }
274
275 const size_t row2 = (static_cast<size_t>(2) * width + i) * State::k_channels;
276 pixels[row2 + 0] = normalize(rec.radius, rs.radius);
277 pixels[row2 + 1] = normalize(rec.query_radius, rs.query_radius);
278
279 const size_t row3 = (static_cast<size_t>(3) * width + i) * State::k_channels;
280 pixels[row3 + 0] = static_cast<float>(rec.id);
281 pixels[row3 + 1] = rec.entity_type_norm;
282 pixels[row3 + 2] = rec.trigger_kind;
283 pixels[row3 + 3] = rec.time_kind;
284
285 const size_t row4 = (static_cast<size_t>(4) * width + i) * State::k_channels;
286 pixels[row4 + 0] = static_cast<float>(rec.sink_type);
287 pixels[row4 + 1] = static_cast<float>(rec.first_audio_channel);
288 }
289
290 image.pixels = std::move(pixels);
291
292 // -------------------------------------------------------------------------
293 // Write EXR.
294 // -------------------------------------------------------------------------
295 const std::string exr_path = base_path + ".exr";
296 auto writer = IO::ImageWriterRegistry::instance().create_writer(exr_path);
297 if (!writer) {
298 m_last_error = "No ImageWriter registered for .exr";
300 return false;
301 }
302
303 IO::ImageWriteOptions options;
304 options.channel_names = { "R", "G", "B", "A" };
305
306 if (!writer->write(exr_path, image, options)) {
307 m_last_error = "EXR write failed: " + writer->get_last_error();
309 return false;
310 }
311
312 // -------------------------------------------------------------------------
313 // Build and write schema.
314 // -------------------------------------------------------------------------
315 State::FabricSchema schema;
316 schema.version = State::k_schema_version;
317 schema.fabric_name = fabric.name();
318 schema.ranges = rs;
319 schema.entities.reserve(records.size());
320
321 for (const auto& rec : records) {
322 State::EntityRecord ent;
323 ent.id = rec.id;
324 ent.kind = State::kind_to_string(rec.kind);
325 ent.position = rec.position;
326 ent.intensity = rec.intensity;
327 ent.radius = rec.radius;
328 ent.query_radius = rec.query_radius;
329 ent.color = rec.color;
330 ent.size = rec.size;
331 ent.influence_fn_name = rec.influence_fn_name;
332 ent.perception_fn_name = rec.perception_fn_name;
333 ent.wiring = build_wiring(fabric, rec.id);
334
335 if (rec.kind == Fabric::Kind::Emitter) {
336 auto e = fabric.get_emitter(rec.id);
337 for (const auto& s : e->audio_sinks())
338 ent.audio_sinks.push_back({ .channel = s.channel, .fn_name = s.fn_name });
339 for (const auto& s : e->render_sinks())
340 ent.render_sinks.push_back({ .fn_name = s.fn_name });
341 } else if (rec.kind == Fabric::Kind::Agent) {
342 auto a = fabric.get_agent(rec.id);
343 for (const auto& s : a->audio_sinks())
344 ent.audio_sinks.push_back({ .channel = s.channel, .fn_name = s.fn_name });
345
346 for (const auto& s : a->render_sinks())
347 ent.render_sinks.push_back({ .fn_name = s.fn_name });
348
349 if (auto locus = std::dynamic_pointer_cast<Locus>(a)) {
350 ent.subkind = "locus";
351 const auto& nav = locus->nav();
352 ent.locus_nav = State::LocusNavRecord {
353 .eye = nav.eye,
354 .target = nav.eye + glm::vec3 { std::cos(nav.pitch) * std::sin(nav.yaw), std::sin(nav.pitch), std::cos(nav.pitch) * std::cos(nav.yaw) },
355 .up = { 0.0F, 1.0F, 0.0F },
356 .fov = nav.fov_radians,
357 .near_plane = nav.near_plane,
358 .far_plane = nav.far_plane,
359 .speed = nav.move_speed,
360 };
361 } else if (auto presence = std::dynamic_pointer_cast<Presence>(a)) {
362 ent.subkind = "presence";
363 ent.radiate_fn_name = presence->radiate_fn_name();
364 ent.falloff_radius = presence->falloff_radius() != presence->query_radius()
365 ? std::optional<float>(presence->falloff_radius())
366 : std::nullopt;
367 if (auto fc = presence->falloff_curve())
368 ent.falloff_curve_name = Reflect::enum_to_lowercase_string(*fc);
369 }
370 }
371
372 schema.entities.push_back(std::move(ent));
373 }
374
375 for (uint32_t xid : fabric.all_expanse_ids()) {
376 const auto x = fabric.get_expanse(xid);
377 if (!x)
378 continue;
379 if (x->fn_name().empty()) {
381 "StateEncoder: Expanse {} has no fn_name, skipping", xid);
382 continue;
383 }
384 schema.expanses.push_back(State::ExpanseRecord {
385 .id = xid,
386 .fn_name = x->fn_name(),
387 .on_enter_fn_name = x->on_enter_fn_name(),
388 .on_exit_fn_name = x->on_exit_fn_name(),
389 });
390 }
391
392 IO::JSONSerializer ser;
393 const std::string json_path = base_path + ".json";
394 if (!ser.write(json_path, schema)) {
395 m_last_error = "Failed to write schema: " + ser.last_error();
397 return false;
398 }
399
401 "StateEncoder: wrote {} entities to {} + {}",
402 records.size(), exr_path, json_path);
403
404 return true;
405}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:57
uint32_t width
Definition Decoder.cpp:59
const std::vector< float > * pixels
Definition Decoder.cpp:58
size_t a
std::unique_ptr< ImageWriter > create_writer(const std::string &filepath) const
static ImageWriterRegistry & instance()
@ FileIO
Filesystem I/O operations.
@ Nexus
Spatial indexing and scheduling for user-defined behaviour.
constexpr uint32_t k_schema_version
Current schema version written by StateEncoder and accepted by StateDecoder.
Definition Schema.hpp:20
constexpr uint32_t k_exr_rows
RGBA32F EXR layout constants shared between encoder and decoder.
Definition Schema.hpp:31
constexpr uint32_t k_channels
Definition Schema.hpp:32
std::string kind_to_string(Fabric::Kind k)
Map Fabric::Kind to its lowercase JSON string token via magic_enum.
Definition Schema.hpp:356
@ RGBA32F
Four channel 32-bit float.
std::string enum_to_lowercase_string(EnumType value) noexcept
Universal enum to lowercase string converter using magic_enum.
void normalize(std::vector< double > &data, double target_peak)
Normalize single-channel data to specified peak level (in-place)
Definition Yantra.cpp:565

References a, MayaFlux::Nexus::Fabric::Agent, MayaFlux::Nexus::Fabric::all_ids(), MayaFlux::Nexus::State::EntityRecord::audio_sinks, MayaFlux::IO::ImageWriteOptions::channel_names, MayaFlux::Nexus::State::EntityRecord::color, MayaFlux::Nexus::State::RangeSet::color_b, MayaFlux::Nexus::State::RangeSet::color_g, MayaFlux::Nexus::State::RangeSet::color_r, MayaFlux::IO::ImageWriterRegistry::create_writer(), MayaFlux::Nexus::Fabric::Emitter, MayaFlux::Nexus::State::FabricSchema::entities, MayaFlux::Nexus::State::FabricSchema::fabric_name, MayaFlux::Journal::FileIO, MayaFlux::Nexus::Fabric::get_agent(), MayaFlux::Nexus::Fabric::get_emitter(), MayaFlux::Nexus::Fabric::get_sensor(), MayaFlux::Nexus::Fabric::id(), MayaFlux::Nexus::State::EntityRecord::id, image, MayaFlux::Nexus::State::EntityRecord::influence_fn_name, MayaFlux::IO::ImageWriterRegistry::instance(), MayaFlux::Nexus::State::RangeSet::intensity, MayaFlux::Nexus::State::EntityRecord::intensity, MayaFlux::Nexus::State::k_channels, MayaFlux::Nexus::State::k_exr_rows, MayaFlux::Nexus::State::k_schema_version, MayaFlux::Nexus::Fabric::kind(), MayaFlux::Nexus::State::EntityRecord::kind, MayaFlux::Nexus::State::kind_to_string(), m_last_error, MF_ERROR, MF_WARN, MayaFlux::Nexus::Fabric::name(), MayaFlux::Journal::Nexus, MayaFlux::normalize(), MayaFlux::Nexus::State::EntityRecord::perception_fn_name, pixels, MayaFlux::Nexus::State::RangeSet::pos_x, MayaFlux::Nexus::State::RangeSet::pos_y, MayaFlux::Nexus::State::RangeSet::pos_z, MayaFlux::Nexus::State::EntityRecord::position, MayaFlux::Nexus::State::RangeSet::query_radius, MayaFlux::Nexus::State::EntityRecord::query_radius, MayaFlux::Nexus::State::RangeSet::radius, MayaFlux::Nexus::State::EntityRecord::radius, MayaFlux::Nexus::State::FabricSchema::ranges, MayaFlux::Nexus::State::EntityRecord::render_sinks, MayaFlux::Portal::Graphics::RGBA32F, MayaFlux::Nexus::Fabric::Sensor, MayaFlux::Nexus::State::RangeSet::size, MayaFlux::Nexus::State::EntityRecord::size, MayaFlux::Nexus::State::FabricSchema::version, MayaFlux::IO::ImageData::width, width, and MayaFlux::Nexus::State::EntityRecord::wiring.

+ Here is the call graph for this function: