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

◆ encode()

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 234 of file StateEncoder.cpp.

235{
236 m_last_error.clear();
237
238 // -------------------------------------------------------------------------
239 // Collect encodable entities.
240 // -------------------------------------------------------------------------
241 struct InternalRecord {
242 uint32_t id;
243 Fabric::Kind kind;
244 glm::vec3 position {};
245 float intensity { 0.0F };
246 float radius { 0.0F };
247 float query_radius { 0.0F };
248 std::optional<glm::vec3> color;
249 std::optional<float> size;
250 std::string influence_fn_name;
251 std::string perception_fn_name;
252 // Layer 0
253 float entity_type_norm { 0.0F };
254 float trigger_kind { 0.0F };
255 float time_kind { 0.0F };
256 // Layer 2
257 uint32_t sink_type { 0 };
258 uint32_t first_audio_channel { 0 };
259 };
260
261 std::vector<InternalRecord> records;
262
263 for (uint32_t id : fabric.all_ids()) {
264 const auto k = fabric.kind(id);
265 switch (k) {
267 auto e = fabric.get_emitter(id);
268 if (!e || !e->position()) {
269 continue;
270 }
271 if (e->fn_name().empty()) {
273 "StateEncoder: Emitter {} has no fn_name", id);
274 }
275 auto& rec = records.emplace_back(InternalRecord {
276 .id = id,
277 .kind = k,
278 .position = *e->position(),
279 .intensity = e->intensity(),
280 .radius = e->radius(),
281 .color = e->color(),
282 .size = e->size(),
283 .influence_fn_name = e->fn_name(),
284 .entity_type_norm = 0.0F,
285 });
286 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
287 rec.sink_type = (e->audio_sinks().empty() ? 0U : 1U)
288 | (e->render_sinks().empty() ? 0U : 2U);
289 if (!e->audio_sinks().empty())
290 rec.first_audio_channel = e->audio_sinks().front().channel;
291 break;
292 }
294 auto s = fabric.get_sensor(id);
295 if (!s || !s->position()) {
296 continue;
297 }
298 if (s->fn_name().empty()) {
300 "StateEncoder: Sensor {} has no fn_name", id);
301 }
302 auto& rec = records.emplace_back(InternalRecord {
303 .id = id,
304 .kind = k,
305 .position = *s->position(),
306 .query_radius = s->query_radius(),
307 .perception_fn_name = s->fn_name(),
308 .entity_type_norm = 0.333F,
309 });
310 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
311 break;
312 }
313 case Fabric::Kind::Agent: {
314 auto a = fabric.get_agent(id);
315 if (!a || !a->position()) {
316 continue;
317 }
318 if (a->perception_fn_name().empty()) {
320 "StateEncoder: Agent {} has no perception_fn_name", id);
321 }
322 if (a->influence_fn_name().empty()) {
324 "StateEncoder: Agent {} has no influence_fn_name", id);
325 }
326 auto& rec = records.emplace_back(InternalRecord {
327 .id = id,
328 .kind = k,
329 .position = *a->position(),
330 .intensity = a->intensity(),
331 .radius = a->radius(),
332 .query_radius = a->query_radius(),
333 .color = a->color(),
334 .size = a->size(),
335 .influence_fn_name = a->influence_fn_name(),
336 .perception_fn_name = a->perception_fn_name(),
337 .entity_type_norm = 0.667F,
338 });
339 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
340 rec.sink_type = (a->audio_sinks().empty() ? 0U : 1U)
341 | (a->render_sinks().empty() ? 0U : 2U);
342 if (!a->audio_sinks().empty())
343 rec.first_audio_channel = a->audio_sinks().front().channel;
344 break;
345 }
346 }
347 }
348
349 if (records.empty()) {
350 m_last_error = "No entities with positions to encode";
352 return false;
353 }
354
355 // -------------------------------------------------------------------------
356 // Compute per-field ranges.
357 // -------------------------------------------------------------------------
358 RangeSet rs;
359 bool init_pos_x = false, init_pos_y = false, init_pos_z = false;
360 bool init_intensity = false, init_radius = false, init_query_radius = false;
361 bool init_color_r = false, init_color_g = false, init_color_b = false;
362 bool init_size = false;
363
364 for (const auto& rec : records) {
365 expand_range(rs.pos_x, rec.position.x, init_pos_x);
366 expand_range(rs.pos_y, rec.position.y, init_pos_y);
367 expand_range(rs.pos_z, rec.position.z, init_pos_z);
368
369 if (rec.kind == Fabric::Kind::Emitter || rec.kind == Fabric::Kind::Agent) {
370 expand_range(rs.intensity, rec.intensity, init_intensity);
371 expand_range(rs.radius, rec.radius, init_radius);
372 if (rec.color) {
373 expand_range(rs.color_r, rec.color->r, init_color_r);
374 expand_range(rs.color_g, rec.color->g, init_color_g);
375 expand_range(rs.color_b, rec.color->b, init_color_b);
376 }
377 if (rec.size) {
378 expand_range(rs.size, *rec.size, init_size);
379 }
380 }
381 if (rec.kind == Fabric::Kind::Sensor || rec.kind == Fabric::Kind::Agent) {
382 expand_range(rs.query_radius, rec.query_radius, init_query_radius);
383 }
384 }
385
386 // -------------------------------------------------------------------------
387 // Build RGBA32F pixel buffer.
388 // -------------------------------------------------------------------------
389 const auto width = static_cast<uint32_t>(records.size());
390
391 IO::ImageData image;
392 image.width = width;
393 image.height = k_exr_rows;
394 image.channels = k_channels;
396
397 std::vector<float> pixels(static_cast<size_t>(width) * k_exr_rows * k_channels, 0.0F);
398
399 for (size_t i = 0; i < records.size(); ++i) {
400 const auto& rec = records[i];
401
402 const size_t row0 = (static_cast<size_t>(0) * width + i) * k_channels;
403 pixels[row0 + 0] = normalize(rec.position.x, rs.pos_x);
404 pixels[row0 + 1] = normalize(rec.position.y, rs.pos_y);
405 pixels[row0 + 2] = normalize(rec.position.z, rs.pos_z);
406 pixels[row0 + 3] = normalize(rec.intensity, rs.intensity);
407
408 const size_t row1 = (static_cast<size_t>(1) * width + i) * k_channels;
409 if (rec.color) {
410 pixels[row1 + 0] = normalize(rec.color->r, rs.color_r);
411 pixels[row1 + 1] = normalize(rec.color->g, rs.color_g);
412 pixels[row1 + 2] = normalize(rec.color->b, rs.color_b);
413 }
414 if (rec.size) {
415 pixels[row1 + 3] = normalize(*rec.size, rs.size);
416 }
417
418 const size_t row2 = (static_cast<size_t>(2) * width + i) * k_channels;
419 pixels[row2 + 0] = normalize(rec.radius, rs.radius);
420 pixels[row2 + 1] = normalize(rec.query_radius, rs.query_radius);
421
422 const size_t row3 = (static_cast<size_t>(3) * width + i) * k_channels;
423 pixels[row3 + 0] = static_cast<float>(rec.id);
424 pixels[row3 + 1] = rec.entity_type_norm;
425 pixels[row3 + 2] = rec.trigger_kind;
426 pixels[row3 + 3] = rec.time_kind;
427
428 const size_t row4 = (static_cast<size_t>(4) * width + i) * k_channels;
429 pixels[row4 + 0] = static_cast<float>(rec.sink_type);
430 pixels[row4 + 1] = static_cast<float>(rec.first_audio_channel);
431 }
432
433 image.pixels = std::move(pixels);
434
435 // -------------------------------------------------------------------------
436 // Write EXR.
437 // -------------------------------------------------------------------------
438 const std::string exr_path = base_path + ".exr";
439 auto writer = IO::ImageWriterRegistry::instance().create_writer(exr_path);
440 if (!writer) {
441 m_last_error = "No ImageWriter registered for .exr";
443 return false;
444 }
445
446 IO::ImageWriteOptions options;
447 options.channel_names = { "R", "G", "B", "A" };
448
449 if (!writer->write(exr_path, image, options)) {
450 m_last_error = "EXR write failed: " + writer->get_last_error();
452 return false;
453 }
454
455 // -------------------------------------------------------------------------
456 // Build and write schema.
457 // -------------------------------------------------------------------------
458 const char* kind_names[] = { "emitter", "sensor", "agent" };
459
460 FabricSchema schema;
461 schema.version = 4;
462 schema.fabric_name = fabric.name();
463 schema.ranges = rs;
464 schema.entities.reserve(records.size());
465
466 for (const auto& rec : records) {
467 EntityRecord ent;
468 ent.id = rec.id;
469 ent.kind = kind_names[static_cast<int>(rec.kind)];
470 ent.position = rec.position;
471 ent.intensity = rec.intensity;
472 ent.radius = rec.radius;
473 ent.query_radius = rec.query_radius;
474 ent.color = rec.color;
475 ent.size = rec.size;
476 ent.influence_fn_name = rec.influence_fn_name;
477 ent.perception_fn_name = rec.perception_fn_name;
478 ent.wiring = build_wiring(fabric, rec.id);
479
480 if (rec.kind == Fabric::Kind::Emitter) {
481 auto e = fabric.get_emitter(rec.id);
482 for (const auto& s : e->audio_sinks())
483 ent.audio_sinks.push_back({ s.channel, s.fn_name });
484 for (const auto& s : e->render_sinks())
485 ent.render_sinks.push_back({ s.fn_name });
486 } else if (rec.kind == Fabric::Kind::Agent) {
487 auto a = fabric.get_agent(rec.id);
488 for (const auto& s : a->audio_sinks())
489 ent.audio_sinks.push_back({ s.channel, s.fn_name });
490 for (const auto& s : a->render_sinks())
491 ent.render_sinks.push_back({ s.fn_name });
492 }
493
494 schema.entities.push_back(std::move(ent));
495 }
496
497 IO::JSONSerializer ser;
498 const std::string json_path = base_path + ".json";
499 if (!ser.write(json_path, schema)) {
500 m_last_error = "Failed to write schema: " + ser.last_error();
502 return false;
503 }
504
506 "StateEncoder: wrote {} entities to {} + {}",
507 records.size(), exr_path, json_path);
508
509 return true;
510}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
size_t a
std::string perception_fn_name
IO::ImageData image
Range intensity
uint32_t width
Range radius
Range query_radius
const std::vector< float > * pixels
Range size
glm::vec3 position
uint32_t id
std::string kind
std::optional< glm::vec3 > color
std::string influence_fn_name
std::vector< RenderSinkRecord > render_sinks
std::vector< AudioSinkRecord > audio_sinks
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.
@ RGBA32F
Four channel 32-bit float.
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::IO::ImageWriteOptions::channel_names, color, MayaFlux::IO::ImageWriterRegistry::create_writer(), MayaFlux::Nexus::Fabric::Emitter, MayaFlux::Journal::FileIO, MayaFlux::Nexus::Fabric::get_agent(), MayaFlux::Nexus::Fabric::get_emitter(), MayaFlux::Nexus::Fabric::get_sensor(), id, image, influence_fn_name, MayaFlux::IO::ImageWriterRegistry::instance(), intensity, MayaFlux::Nexus::Fabric::kind(), kind, m_last_error, MF_ERROR, MF_WARN, MayaFlux::Nexus::Fabric::name(), MayaFlux::Journal::Nexus, MayaFlux::normalize(), perception_fn_name, pixels, position, query_radius, radius, MayaFlux::Portal::Graphics::RGBA32F, MayaFlux::Nexus::Fabric::Sensor, size, MayaFlux::IO::ImageData::width, and width.

+ Here is the call graph for this function: