235{
237
238
239
240
241 struct InternalRecord {
248 std::optional<glm::vec3>
color;
249 std::optional<float>
size;
252
253 float entity_type_norm { 0.0F };
254 float trigger_kind { 0.0F };
255 float time_kind { 0.0F };
256
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 {
277 .kind = k,
278 .position = *e->position(),
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() ? 0
U : 1U)
288 | (e->render_sinks().empty() ? 0
U : 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 {
304 .kind = k,
305 .position = *s->position(),
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 }
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 {
328 .kind = k,
329 .position = *
a->position(),
331 .radius =
a->radius(),
332 .query_radius =
a->query_radius(),
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() ? 0
U : 1U)
341 | (
a->render_sinks().empty() ? 0
U : 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()) {
352 return false;
353 }
354
355
356
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
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 }
382 expand_range(rs.query_radius, rec.query_radius, init_query_radius);
383 }
384 }
385
386
387
388
389 const auto width =
static_cast<uint32_t
>(records.size());
390
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;
407
408 const size_t row1 = (
static_cast<size_t>(1) *
width + i) * k_channels;
409 if (rec.color) {
413 }
414 if (rec.size) {
416 }
417
418 const size_t row2 = (
static_cast<size_t>(2) *
width + i) * k_channels;
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
434
435
436
437
438 const std::string exr_path = base_path + ".exr";
440 if (!writer) {
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
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
481 auto e = fabric.get_emitter(rec.id);
483 ent.
audio_sinks.push_back({ s.channel, s.fn_name });
487 auto a = fabric.get_agent(rec.id);
489 ent.
audio_sinks.push_back({ s.channel, 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,...)
std::string perception_fn_name
const std::vector< float > * pixels
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)