MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Encoder.cpp
Go to the documentation of this file.
1#include "Encoder.hpp"
2
4
7
9
12
15
17
19
20namespace MayaFlux::Nexus {
21
22namespace {
23
24 // -------------------------------------------------------------------------
25 // State::Range helpers
26 // -------------------------------------------------------------------------
27
28 void expand_range(State::Range& r, float value, bool& initialized)
29 {
30 if (!initialized) {
31 r.min = r.max = value;
32 initialized = true;
33 } else {
34 r.min = std::min(r.min, value);
35 r.max = std::max(r.max, value);
36 }
37 }
38
39 float normalize(float value, const State::Range& r)
40 {
41 if (r.max <= r.min) {
42 return 0.0F;
43 }
44 return (value - r.min) / (r.max - r.min);
45 }
46
47 // -------------------------------------------------------------------------
48 // Sink records
49 // -------------------------------------------------------------------------
50
51 void fill_sinks(
52 State::EntityRecord& ent,
53 const std::vector<AudioSink>& audio_sinks,
54 const std::vector<RenderSink>& render_sinks)
55 {
56 for (const auto& s : audio_sinks)
57 ent.audio_sinks.push_back({ .channel = s.channel, .fn_name = s.fn_name });
58
59 for (const auto& s : render_sinks) {
60 State::RenderSinkRecord rec { .fn_name = s.fn_name };
61 if (s.buf) {
62 const auto config = s.buf->get_render_config();
63 rec.vertex_shader = config.vertex_shader;
64 rec.fragment_shader = config.fragment_shader;
65 rec.geometry_shader = config.geometry_shader;
66 rec.default_texture_binding = config.default_texture_binding;
67 rec.topology = config.topology;
68 rec.polygon_mode = config.polygon_mode;
69 rec.cull_mode = config.cull_mode;
70 rec.triangulate = config.triangulate;
71 rec.draw_priority = config.draw_priority;
72 rec.extra_string_params = config.extra_string_params;
73 }
74 ent.render_sinks.push_back(std::move(rec));
75 }
76 }
77
78 void fill_wiring_pixels(const Fabric& fabric, uint32_t id, float& trigger_out, float& time_out)
79 {
80 const Wiring* w = fabric.wiring_for(id);
81 trigger_out = 0.0F;
82 time_out = 0.0F;
83 if (!w)
84 return;
85 if (!w->move_steps().empty()) {
86 time_out = 1.0F;
87 } else if (w->interval().has_value()) {
88 trigger_out = 0.2F;
89 if (w->duration().has_value())
90 time_out = 0.5F;
91 }
92 }
93
94} // namespace
95
97{
98 const Wiring* w = fabric.wiring_for(id);
99 if (!w)
100 return { .kind = State::WiringKind::Unsupported };
101
104
105 const auto take_times = [&] {
106 if (w->times_count() > 1)
107 rec.times = w->times_count();
108 };
109 const auto take_duration = [&] {
110 if (w->duration()) {
111 rec.duration = w->duration();
113 }
114 };
115
116 if (!std::holds_alternative<std::monostate>(w->factory()) || w->event_factory()) {
118 rec.factory_name = w->factory_name();
119
120 } else if (w->has_bind()) {
124 take_duration();
125
126 } else if (!w->move_steps().empty()) {
128 std::vector<State::WiringStep> steps;
129 steps.reserve(w->move_steps().size());
130 for (const auto& s : w->move_steps())
131 steps.push_back({ .position = s.position, .delay_seconds = s.delay_seconds });
132 rec.steps = std::move(steps);
133 take_times();
134
135 } else if (!std::holds_alternative<std::monostate>(w->trigger())) {
136 std::visit([&rec](const auto& trigger) {
137 using T = std::decay_t<decltype(trigger)>;
138 if constexpr (std::is_same_v<T, Wiring::KeyTrigger>) {
139 rec.kind = State::WiringKind::Key;
140 rec.key = trigger.key;
141 rec.held = trigger.held;
142 } else if constexpr (std::is_same_v<T, Wiring::MouseTrigger>) {
143 rec.kind = State::WiringKind::Mouse;
144 rec.button = trigger.button;
145 rec.held = trigger.held;
146 } else if constexpr (std::is_same_v<T, Wiring::ScrollTrigger>) {
147 rec.kind = State::WiringKind::Scroll;
148 } else if constexpr (std::is_same_v<T, Wiring::NetworkTrigger>) {
150 } else if constexpr (std::is_same_v<T, Wiring::WindowEventTrigger>) {
152 } else {
154 }
155 },
156 w->trigger());
157
158 } else if (w->interval()) {
159 rec.kind = State::WiringKind::Every;
160 rec.interval = w->interval();
161 rec.interval_token = w->m_metro_token;
162 take_duration();
163 take_times();
164 }
165
166 return rec;
167}
168
169template <typename Record>
170void StateEncoder::fill_expanse(Record& record, const Expanse& expanse)
171{
172 record.fn_name = expanse.fn_name();
173 record.on_enter_fn_name = expanse.on_enter_fn_name();
174 record.on_exit_fn_name = expanse.on_exit_fn_name();
175 if (expanse.bounds()) {
176 record.bounds_min = expanse.bounds()->min;
177 record.bounds_max = expanse.bounds()->max;
178 }
179
180 if (const auto* hull = dynamic_cast<const Hull*>(&expanse)) {
181 record.subkind = "hull";
182 record.collection = hull->m_collection;
183 } else if (const auto* mantle = dynamic_cast<const Mantle*>(&expanse)) {
184 record.subkind = "mantle";
185 record.fit_name = Reflect::enum_to_lowercase_string(mantle->m_fit);
186 }
187
188 if (!record.subkind.empty()) {
190 "StateEncoder: {} wraps a live buffer that is not encoded", record.subkind);
191 }
192}
193
194bool StateEncoder::encode(const Fabric& fabric, const std::string& base_path)
195{
196 return encode_fabric(fabric, base_path, {});
197}
198
199bool StateEncoder::encode_fabric(
200 const Fabric& fabric,
201 const std::string& base_path,
202 const std::unordered_set<const Expanse*>& tapestry_owned)
203{
204 m_last_error.clear();
205
206 // -------------------------------------------------------------------------
207 // Collect encodable entities.
208 // -------------------------------------------------------------------------
209 struct InternalRecord {
210 uint32_t id;
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) {
238 case Fabric::Kind::Emitter: {
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 }
265 case Fabric::Kind::Sensor: {
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 // -------------------------------------------------------------------------
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) {
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();
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()) {
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
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}
525
526bool StateEncoder::encode(const Tapestry& tapestry, const std::string& base_dir, nlohmann::json user_state)
527{
528 m_last_error.clear();
529
531
532 std::unordered_set<const Expanse*> tapestry_owned;
533 for (const auto& [xname, xptr] : tapestry.all_expanses()) {
534 tapestry_owned.insert(xptr.get());
535 }
536
537 for (const auto& fabric : tapestry.all_fabrics()) {
538 const std::string fabric_id = fabric->name().empty()
539 ? std::to_string(fabric->id())
540 : fabric->name();
541
542 const std::string base_path = base_dir + "/" + fabric_id;
543
544 if (!encode_fabric(*fabric, base_path, tapestry_owned)) {
545 return false;
546 }
547
548 schema.fabrics.push_back(State::FabricRef {
549 .name = fabric_id,
550 .base_path = base_path,
551 });
552 }
553
554 for (const auto& [xname, xptr] : tapestry.all_expanses()) {
555 State::TapestryExpanseRecord xrec { .name = xname };
556 fill_expanse(xrec, *xptr);
557 for (const auto& fabric : tapestry.all_fabrics()) {
558 for (uint32_t xid : fabric->all_expanse_ids()) {
559 if (fabric->get_expanse(xid) == xptr) {
560 const std::string fname = fabric->name().empty()
561 ? std::to_string(fabric->id())
562 : fabric->name();
563 xrec.fabric_names.push_back(fname);
564 break;
565 }
566 }
567 }
568 schema.expanses.push_back(std::move(xrec));
569 }
570
571 schema.user_state = std::move(user_state);
572
574 const std::string tapestry_path = base_dir + "/tapestry.json";
575 if (!ser.write(tapestry_path, schema)) {
576 m_last_error = "Failed to write tapestry schema: " + ser.last_error();
578 return false;
579 }
580
582 "StateEncoder: wrote {} fabrics to {}", schema.fabrics.size(), tapestry_path);
583 return true;
584}
585
586} // namespace MayaFlux::Nexus
#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
glm::vec3 value
uint32_t width
const std::string & last_error() const
Last error message, empty if no error.
bool write(const std::string &path, const T &value, int indent=2)
Encode value and write to path (created or truncated).
Converts arbitrary C++ types to/from JSON strings and disk files.
static WriterRegistry & instance()
std::unique_ptr< Writer > create_writer(const std::string &filepath) const
const std::string & on_enter_fn_name() const
Identifier assigned to the on_enter callable, empty if anonymous.
Definition Expanse.hpp:162
const std::string & on_exit_fn_name() const
Identifier assigned to the on_exit callable, empty if anonymous.
Definition Expanse.hpp:168
const std::optional< Kinesis::AABB3D > & bounds() const
The box, if set.
Definition Expanse.hpp:153
const std::string & fn_name() const
Identifier assigned to the containment predicate, empty if anonymous.
Definition Expanse.hpp:156
A defined region of space with a containment test and crossing actions.
Definition Expanse.hpp:55
std::shared_ptr< Sensor > get_sensor(uint32_t id) const
Get the Sensor registered under id.
Definition Fabric.cpp:156
std::vector< uint32_t > all_ids() const
List all registered entity ids in insertion order.
Definition Fabric.cpp:119
std::shared_ptr< Expanse > get_expanse(uint32_t id) const
Look up a registered Expanse by id.
Definition Fabric.cpp:377
std::shared_ptr< Agent > get_agent(uint32_t id) const
Get the Agent registered under id.
Definition Fabric.cpp:167
const Wiring * wiring_for(uint32_t id) const
Access the finalised wiring for an entity for introspection.
Definition Fabric.cpp:255
const std::string & name() const
Assigned name, empty if the Fabric was constructed outside a Tapestry.
Definition Fabric.hpp:67
Kind kind(uint32_t id) const
Return the kind of entity registered under id.
Definition Fabric.cpp:129
std::vector< uint32_t > all_expanse_ids() const
Stable ids of all registered Expanses in insertion order.
Definition Fabric.cpp:368
uint32_t id() const
Stable id for this Fabric, assigned by Tapestry at construction.
Definition Fabric.hpp:77
std::shared_ptr< Emitter > get_emitter(uint32_t id) const
Get the Emitter registered under id.
Definition Fabric.cpp:145
Orchestrates spatial indexing and scheduling for Nexus objects.
Definition Fabric.hpp:38
An Expanse whose region is the bounds of a buffer that already exists.
Definition Hull.hpp:28
An Expanse that is a box with a buffer that already exists as its visible body.
Definition Mantle.hpp:33
static State::WiringRecord build_wiring(const Fabric &fabric, uint32_t id)
Definition Encoder.cpp:96
const std::vector< std::shared_ptr< Fabric > > & all_fabrics() const
All woven Fabrics, named and unnamed.
Definition Tapestry.cpp:79
const std::unordered_map< std::string, std::shared_ptr< Expanse > > & all_expanses() const
Read-only view of all Expanses owned by this Tapestry.
Definition Tapestry.hpp:83
Owner of one or more Fabrics and the shared state they rely on.
Definition Tapestry.hpp:25
const std::string & position_fn_name() const
Name of the position function, empty if anonymous.
Definition Wiring.hpp:378
const std::string & bind_attach_name() const
Name of the bind attach function, empty if anonymous or none.
Definition Wiring.hpp:384
const std::string & factory_name() const
Name of the active factory, empty if anonymous or none.
Definition Wiring.hpp:381
const EFactory & event_factory() const
Active event-factory, if use(EventFactory) was called.
Definition Wiring.hpp:366
const std::vector< MoveStep > & move_steps() const
Choreography steps from move_to calls.
Definition Wiring.hpp:354
const std::string & bind_detach_name() const
Name of the bind detach function, empty if anonymous or none.
Definition Wiring.hpp:387
Vruta::ProcessingToken m_duration_token
Definition Wiring.hpp:313
const Factory & factory() const
Active factory variant set by use for non-Event factories.
Definition Wiring.hpp:363
bool has_bind() const
True if any bind overload was called.
Definition Wiring.hpp:372
std::optional< double > duration() const
Active duration in seconds, if for_duration was called.
Definition Wiring.hpp:348
size_t times_count() const
Repetition count set by times, default 1.
Definition Wiring.hpp:351
Fluent builder that wires an entity into Fabric's scheduling infrastructure.
Definition Wiring.hpp:42
@ 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
std::string enum_to_lowercase_string(EnumType value) noexcept
Universal enum to lowercase string converter using magic_enum.
std::atomic< bool > initialized
Definition Core.cpp:21
Configuration for image writing.
Raw image data loaded from file or read back from a GPU image.
Definition ImageData.hpp:55
std::optional< LocusNavRecord > locus_nav
Definition Schema.hpp:283
std::optional< float > size
Definition Schema.hpp:276
std::optional< glm::vec3 > color
Definition Schema.hpp:275
std::vector< AudioSinkRecord > audio_sinks
Definition Schema.hpp:281
std::optional< float > falloff_radius
Definition Schema.hpp:285
std::optional< glm::vec3 > position
Definition Schema.hpp:270
std::optional< glm::vec4 > orientation
Definition Schema.hpp:271
Per-entity JSON record.
Definition Schema.hpp:266
Per-expanse JSON record.
Definition Schema.hpp:328
Entry in the Tapestry envelope pointing to one Fabric's EXR+JSON pair.
Definition Schema.hpp:385
std::vector< EntityRecord > entities
Definition Schema.hpp:361
std::optional< float > cell_size
Definition Schema.hpp:364
std::vector< ExpanseRecord > expanses
Definition Schema.hpp:362
Serializable subset of Kinesis::NavigationConfig sufficient to seed a Locus on reconstruct.
Definition Schema.hpp:227
Tapestry-level named Expanse record.
Definition Schema.hpp:407
std::vector< FabricRef > fabrics
Definition Schema.hpp:437
std::vector< TapestryExpanseRecord > expanses
Definition Schema.hpp:438
std::optional< size_t > times
Definition Schema.hpp:133
std::optional< Vruta::ProcessingToken > duration_token
Definition Schema.hpp:136
std::optional< std::vector< WiringStep > > steps
Definition Schema.hpp:134
std::optional< double > duration
Definition Schema.hpp:132