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

◆ encode_fabric()

bool MayaFlux::Nexus::StateEncoder::encode_fabric ( const Fabric &  fabric,
const std::string &  base_path,
const std::unordered_set< const Expanse * > &  tapestry_owned 
)
private

Definition at line 199 of file Encoder.cpp.

203{
204 m_last_error.clear();
205
206 // -------------------------------------------------------------------------
207 // Collect encodable entities.
208 // -------------------------------------------------------------------------
209 struct InternalRecord {
210 uint32_t id;
211 Fabric::Kind kind;
212 std::optional<glm::vec3> position;
213 std::optional<glm::vec4> orientation;
214 float intensity { 0.0F };
215 float radius { 0.0F };
216 float query_radius { 0.0F };
217 std::optional<glm::vec3> color;
218 std::optional<float> size;
219 std::string influence_fn_name;
220 std::string perception_fn_name;
221 // Layer 0
222 float entity_type_norm { 0.0F };
223 float trigger_kind { 0.0F };
224 float time_kind { 0.0F };
225 // Layer 2
226 uint32_t sink_type { 0 };
227 uint32_t first_audio_channel { 0 };
228 };
229
230 std::vector<InternalRecord> records;
231
232 auto ids = fabric.all_ids();
233 std::ranges::sort(ids);
234
235 for (uint32_t id : ids) {
236 const auto k = fabric.kind(id);
237 switch (k) {
239 auto e = fabric.get_emitter(id);
240 if (!e) {
241 continue;
242 }
243 if (e->fn() && e->fn_name().empty()) {
245 "StateEncoder: Emitter {} has a function but no fn_name", id);
246 }
247 auto& rec = records.emplace_back(InternalRecord {
248 .id = id,
249 .kind = k,
250 .position = e->position(),
251 .intensity = e->intensity(),
252 .radius = e->radius(),
253 .color = e->color(),
254 .size = e->size(),
255 .influence_fn_name = e->fn_name(),
256 .entity_type_norm = 0.0F,
257 });
258 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
259 rec.sink_type = (e->audio_sinks().empty() ? 0U : 1U)
260 | (e->render_sinks().empty() ? 0U : 2U);
261 if (!e->audio_sinks().empty())
262 rec.first_audio_channel = e->audio_sinks().front().channel;
263 break;
264 }
266 auto s = fabric.get_sensor(id);
267 if (!s) {
268 continue;
269 }
270 if (s->fn() && s->fn_name().empty()) {
272 "StateEncoder: Sensor {} has a function but no fn_name", id);
273 }
274 auto& rec = records.emplace_back(InternalRecord {
275 .id = id,
276 .kind = k,
277 .position = s->position(),
278 .query_radius = s->query_radius(),
279 .perception_fn_name = s->fn_name(),
280 .entity_type_norm = 0.333F,
281 });
282 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
283 break;
284 }
285 case Fabric::Kind::Agent: {
286 auto a = fabric.get_agent(id);
287 if (!a) {
288 continue;
289 }
290 if (a->perception_fn() && a->perception_fn_name().empty()) {
292 "StateEncoder: Agent {} has a perception function but no perception_fn_name", id);
293 }
294 if (a->influence_fn() && a->influence_fn_name().empty()) {
296 "StateEncoder: Agent {} has an influence function but no influence_fn_name", id);
297 }
298 auto& rec = records.emplace_back(InternalRecord {
299 .id = id,
300 .kind = k,
301 .position = a->position(),
302 .intensity = a->intensity(),
303 .radius = a->radius(),
304 .query_radius = a->query_radius(),
305 .color = a->color(),
306 .size = a->size(),
307 .influence_fn_name = a->influence_fn_name(),
308 .perception_fn_name = a->perception_fn_name(),
309 .entity_type_norm = 0.667F,
310 });
311 if (const auto& q = a->orientation()) {
312 rec.orientation = glm::vec4(q->x, q->y, q->z, q->w);
313 }
314 fill_wiring_pixels(fabric, id, rec.trigger_kind, rec.time_kind);
315 rec.sink_type = (a->audio_sinks().empty() ? 0U : 1U)
316 | (a->render_sinks().empty() ? 0U : 2U);
317 if (!a->audio_sinks().empty())
318 rec.first_audio_channel = a->audio_sinks().front().channel;
319 break;
320 }
321 }
322 }
323
324 if (records.empty()) {
325 m_last_error = "No entities to encode";
327 return false;
328 }
329
330 // -------------------------------------------------------------------------
331 // Compute per-field ranges.
332 // -------------------------------------------------------------------------
333 State::RangeSet rs;
334 bool init_pos_x = false, init_pos_y = false, init_pos_z = false;
335 bool init_intensity = false, init_radius = false, init_query_radius = false;
336 bool init_color_r = false, init_color_g = false, init_color_b = false;
337 bool init_size = false;
338
339 for (const auto& rec : records) {
340 if (rec.position) {
341 expand_range(rs.pos_x, rec.position->x, init_pos_x);
342 expand_range(rs.pos_y, rec.position->y, init_pos_y);
343 expand_range(rs.pos_z, rec.position->z, init_pos_z);
344 }
345
346 if (rec.kind == Fabric::Kind::Emitter || rec.kind == Fabric::Kind::Agent) {
347 expand_range(rs.intensity, rec.intensity, init_intensity);
348 expand_range(rs.radius, rec.radius, init_radius);
349 if (rec.color) {
350 expand_range(rs.color_r, rec.color->r, init_color_r);
351 expand_range(rs.color_g, rec.color->g, init_color_g);
352 expand_range(rs.color_b, rec.color->b, init_color_b);
353 }
354 if (rec.size) {
355 expand_range(rs.size, *rec.size, init_size);
356 }
357 }
358 if (rec.kind == Fabric::Kind::Sensor || rec.kind == Fabric::Kind::Agent) {
359 expand_range(rs.query_radius, rec.query_radius, init_query_radius);
360 }
361 }
362
363 // -------------------------------------------------------------------------
364 // Build RGBA32F pixel buffer.
365 // -------------------------------------------------------------------------
366 const auto width = static_cast<uint32_t>(records.size());
367
369 image.width = width;
370 image.height = State::k_exr_rows;
371 image.channels = State::k_channels;
372 image.format = Portal::Graphics::ImageFormat::RGBA32F;
373
374 std::vector<float> pixels(static_cast<size_t>(width) * State::k_exr_rows * State::k_channels, 0.0F);
375
376 for (size_t i = 0; i < records.size(); ++i) {
377 const auto& rec = records[i];
378
379 const size_t row0 = (static_cast<size_t>(0) * width + i) * State::k_channels;
380 if (rec.position) {
381 pixels[row0 + 0] = normalize(rec.position->x, rs.pos_x);
382 pixels[row0 + 1] = normalize(rec.position->y, rs.pos_y);
383 pixels[row0 + 2] = normalize(rec.position->z, rs.pos_z);
384 }
385 pixels[row0 + 3] = normalize(rec.intensity, rs.intensity);
386
387 const size_t row1 = (static_cast<size_t>(1) * width + i) * State::k_channels;
388 if (rec.color) {
389 pixels[row1 + 0] = normalize(rec.color->r, rs.color_r);
390 pixels[row1 + 1] = normalize(rec.color->g, rs.color_g);
391 pixels[row1 + 2] = normalize(rec.color->b, rs.color_b);
392 }
393 if (rec.size) {
394 pixels[row1 + 3] = normalize(*rec.size, rs.size);
395 }
396
397 const size_t row2 = (static_cast<size_t>(2) * width + i) * State::k_channels;
398 pixels[row2 + 0] = normalize(rec.radius, rs.radius);
399 pixels[row2 + 1] = normalize(rec.query_radius, rs.query_radius);
400
401 const size_t row3 = (static_cast<size_t>(3) * width + i) * State::k_channels;
402 pixels[row3 + 0] = static_cast<float>(rec.id);
403 pixels[row3 + 1] = rec.entity_type_norm;
404 pixels[row3 + 2] = rec.trigger_kind;
405 pixels[row3 + 3] = rec.time_kind;
406
407 const size_t row4 = (static_cast<size_t>(4) * width + i) * State::k_channels;
408 pixels[row4 + 0] = static_cast<float>(rec.sink_type);
409 pixels[row4 + 1] = static_cast<float>(rec.first_audio_channel);
410 }
411
412 image.pixels = std::move(pixels);
413
414 // -------------------------------------------------------------------------
415 // Write EXR.
416 // -------------------------------------------------------------------------
417 const std::string exr_path = base_path + ".exr";
418 auto writer = IO::ImageWriterRegistry::instance().create_writer(exr_path);
419 if (!writer) {
420 m_last_error = "No ImageWriter registered for .exr";
422 return false;
423 }
424
425 IO::ImageWriteOptions options;
426
427 if (!writer->write(exr_path, image, options)) {
428 m_last_error = "EXR write failed: " + writer->get_last_error();
430 return false;
431 }
432
433 // -------------------------------------------------------------------------
434 // Build and write schema.
435 // -------------------------------------------------------------------------
436 State::FabricSchema schema;
437 schema.version = State::k_schema_version;
438 schema.fabric_name = fabric.name();
439 schema.cell_size = fabric.m_cell_size;
440 schema.ranges = rs;
441 schema.entities.reserve(records.size());
442
443 for (const auto& rec : records) {
444 State::EntityRecord ent;
445 ent.id = rec.id;
446 ent.kind = State::kind_to_string(rec.kind);
447 ent.position = rec.position;
448 ent.orientation = rec.orientation;
449 ent.intensity = rec.intensity;
450 ent.radius = rec.radius;
451 ent.query_radius = rec.query_radius;
452 ent.color = rec.color;
453 ent.size = rec.size;
454 ent.influence_fn_name = rec.influence_fn_name;
455 ent.perception_fn_name = rec.perception_fn_name;
456 ent.wiring = build_wiring(fabric, rec.id);
457
458 if (rec.kind == Fabric::Kind::Emitter) {
459 auto e = fabric.get_emitter(rec.id);
460 fill_sinks(ent, e->audio_sinks(), e->render_sinks());
461 } else if (rec.kind == Fabric::Kind::Agent) {
462 auto a = fabric.get_agent(rec.id);
463 fill_sinks(ent, a->audio_sinks(), a->render_sinks());
464
465 if (auto locus = std::dynamic_pointer_cast<Locus>(a)) {
466 ent.subkind = "locus";
467 const auto& nav = locus->nav();
468 ent.locus_nav = State::LocusNavRecord {
469 .eye = nav.eye,
470 .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) },
471 .fov = nav.fov_radians,
472 .near_plane = nav.near_plane,
473 .far_plane = nav.far_plane,
474 .speed = nav.move_speed,
475 .mouse_sensitivity = nav.mouse_sensitivity,
476 .scroll_speed = nav.scroll_speed,
477 .aspect = locus->aspect(),
478 };
479 } else if (auto presence = std::dynamic_pointer_cast<Presence>(a)) {
480 ent.subkind = "presence";
481 ent.radiate_fn_name = presence->radiate_fn_name();
482 ent.falloff_radius = presence->falloff_radius() != presence->query_radius()
483 ? std::optional<float>(presence->falloff_radius())
484 : std::nullopt;
485 if (auto fc = presence->falloff_curve()) {
486 ent.falloff_curve_name = Reflect::enum_to_lowercase_string(*fc);
487 } else {
489 "StateEncoder: Presence {} has a custom falloff, which is not encoded", rec.id);
490 }
491 }
492 }
493
494 schema.entities.push_back(std::move(ent));
495 }
496
497 for (uint32_t xid : fabric.all_expanse_ids()) {
498 const auto x = fabric.get_expanse(xid);
499 if (!x || tapestry_owned.contains(x.get()))
500 continue;
501 State::ExpanseRecord record { .id = xid };
502 fill_expanse(record, *x);
503 if (record.subkind.empty() && record.fn_name.empty() && !record.bounds_min) {
505 "StateEncoder: Expanse {} has neither fn_name nor bounds, skipping", xid);
506 continue;
507 }
508 schema.expanses.push_back(std::move(record));
509 }
510
511 IO::JSONSerializer ser;
512 const std::string json_path = base_path + ".json";
513 if (!ser.write(json_path, schema)) {
514 m_last_error = "Failed to write schema: " + ser.last_error();
516 return false;
517 }
518
520 "StateEncoder: wrote {} entities to {} + {}",
521 records.size(), exr_path, json_path);
522
523 return true;
524}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
glm::vec3 color
Definition Decoder.cpp:97
IO::ImageData image
Definition Decoder.cpp:47
std::optional< glm::vec3 > position
Definition Decoder.cpp:93
float intensity
Definition Decoder.cpp:94
float query_radius
Definition Decoder.cpp:96
const std::vector< float > * pixels
Definition Decoder.cpp:48
bool size
float radius
size_t a
double q
std::vector< uint64_t > ids
AttributeKind kind
uint32_t width
static WriterRegistry & instance()
std::unique_ptr< Writer > create_writer(const std::string &filepath) const
static void fill_expanse(Record &record, const Expanse &expanse)
Definition Encoder.cpp:170
static State::WiringRecord build_wiring(const Fabric &fabric, uint32_t id)
Definition Encoder.cpp:96
Kakshya::ImageData ImageData
@ FileIO
Filesystem I/O operations.
@ Nexus
Spatial indexing and scheduling for user-defined behaviour.
void normalize(std::span< double > data, double target_min, double target_max) noexcept
Normalize to [target_min, target_max] in-place Single-pass min/max reduction followed by a single tra...
Definition Transform.cpp:61
constexpr uint32_t k_schema_version
Current schema version written by StateEncoder and accepted by StateDecoder.
Definition Schema.hpp:26
constexpr uint32_t k_exr_rows
RGBA32F EXR layout constants shared between encoder and decoder.
Definition Schema.hpp:47
constexpr uint32_t k_channels
Definition Schema.hpp:48
std::string kind_to_string(Fabric::Kind k)
Map Fabric::Kind to its lowercase JSON string token via magic_enum.
Definition Schema.hpp:458
std::string enum_to_lowercase_string(EnumType value) noexcept
Universal enum to lowercase string converter using magic_enum.

References a, MayaFlux::Nexus::Fabric::all_expanse_ids(), MayaFlux::Nexus::Fabric::all_ids(), MayaFlux::Nexus::State::EntityRecord::audio_sinks, MayaFlux::Nexus::State::FabricSchema::cell_size, MayaFlux::Kakshya::ImageData::channels, color, 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::WriterRegistry< Writer >::create_writer(), MayaFlux::Nexus::State::FabricSchema::entities, MayaFlux::Reflect::enum_to_lowercase_string(), MayaFlux::Nexus::State::FabricSchema::expanses, MayaFlux::Nexus::State::LocusNavRecord::eye, MayaFlux::Nexus::State::FabricSchema::fabric_name, MayaFlux::Nexus::State::EntityRecord::falloff_curve_name, MayaFlux::Nexus::State::EntityRecord::falloff_radius, MayaFlux::Journal::FileIO, MayaFlux::Kakshya::ImageData::format, MayaFlux::Nexus::Fabric::get_agent(), MayaFlux::Nexus::Fabric::get_emitter(), MayaFlux::Nexus::Fabric::get_expanse(), MayaFlux::Nexus::Fabric::get_sensor(), MayaFlux::Kakshya::ImageData::height, MayaFlux::Nexus::Fabric::id(), MayaFlux::Nexus::State::EntityRecord::id, MayaFlux::Nexus::State::ExpanseRecord::id, ids, image, MayaFlux::Nexus::State::EntityRecord::influence_fn_name, MayaFlux::IO::WriterRegistry< Writer >::instance(), intensity, MayaFlux::Nexus::State::RangeSet::intensity, MayaFlux::Nexus::State::EntityRecord::intensity, kind, MayaFlux::Nexus::Fabric::kind(), MayaFlux::Nexus::State::EntityRecord::kind, MayaFlux::IO::JSONSerializer::last_error(), MayaFlux::Nexus::State::EntityRecord::locus_nav, MayaFlux::Nexus::Fabric::m_cell_size, MF_ERROR, MF_INFO, MF_WARN, MayaFlux::Nexus::Fabric::name(), MayaFlux::Journal::Nexus, MayaFlux::Nexus::State::EntityRecord::orientation, MayaFlux::Nexus::State::EntityRecord::perception_fn_name, MayaFlux::Kakshya::ImageData::pixels, pixels, MayaFlux::Nexus::State::RangeSet::pos_x, MayaFlux::Nexus::State::RangeSet::pos_y, MayaFlux::Nexus::State::RangeSet::pos_z, position, MayaFlux::Nexus::State::EntityRecord::position, q, query_radius, MayaFlux::Nexus::State::RangeSet::query_radius, MayaFlux::Nexus::State::EntityRecord::query_radius, MayaFlux::Nexus::State::EntityRecord::radiate_fn_name, radius, MayaFlux::Nexus::State::RangeSet::radius, MayaFlux::Nexus::State::EntityRecord::radius, MayaFlux::Nexus::State::FabricSchema::ranges, size, MayaFlux::Nexus::State::RangeSet::size, MayaFlux::Nexus::State::EntityRecord::size, MayaFlux::Nexus::State::EntityRecord::subkind, MayaFlux::Nexus::State::FabricSchema::version, width, MayaFlux::Kakshya::ImageData::width, MayaFlux::Nexus::State::EntityRecord::wiring, and MayaFlux::IO::JSONSerializer::write().

+ Here is the call graph for this function: