13 constexpr uint32_t k_exr_rows = 5;
14 constexpr uint32_t k_channels = 4;
24 static constexpr auto describe()
26 return std::make_tuple(
40 static constexpr auto describe()
42 return std::make_tuple(
52 IO::member(
"query_radius", &RangeSet::query_radius));
60 static constexpr auto describe()
62 return std::make_tuple(
64 IO::member(
"delay", &WiringStep::delay_seconds));
69 std::string
kind {
"commit_driven" };
73 std::optional<std::vector<WiringStep>>
steps;
75 static constexpr auto describe()
77 return std::make_tuple(
86 struct AudioSinkRecord {
90 static constexpr auto describe()
92 return std::make_tuple(
93 IO::member(
"channel", &AudioSinkRecord::channel),
94 IO::member(
"fn_name", &AudioSinkRecord::fn_name));
98 struct RenderSinkRecord {
101 static constexpr auto describe()
103 return std::make_tuple(
104 IO::member(
"fn_name", &RenderSinkRecord::fn_name));
108 struct EntityRecord {
116 std::optional<float>
size;
123 static constexpr auto describe()
125 return std::make_tuple(
128 IO::member(
"position", &EntityRecord::position),
129 IO::member(
"intensity", &EntityRecord::intensity),
131 IO::member(
"query_radius", &EntityRecord::query_radius),
134 IO::member(
"influence_fn_name", &EntityRecord::influence_fn_name),
135 IO::member(
"perception_fn_name", &EntityRecord::perception_fn_name),
137 IO::member(
"audio_sinks", &EntityRecord::audio_sinks),
138 IO::member(
"render_sinks", &EntityRecord::render_sinks));
142 struct FabricSchema {
148 static constexpr auto describe()
150 return std::make_tuple(
151 IO::member(
"version", &FabricSchema::version),
152 IO::member(
"fabric_name", &FabricSchema::fabric_name),
153 IO::member(
"entities", &FabricSchema::entities),
162 void expand_range(Range& r,
float value,
bool& initialized)
165 r.min = r.max = value;
168 r.min = std::min(r.min, value);
169 r.max = std::max(r.max, value);
173 float normalize(
float value,
const Range& r)
175 if (r.max <= r.min) {
178 return (value - r.min) / (r.max - r.min);
185 WiringRecord build_wiring(
const Fabric& fabric, uint32_t
id)
187 const Wiring* w = fabric.wiring_for(
id);
189 return { .kind =
"unsupported" };
191 if (!w->move_steps().empty()) {
192 std::vector<WiringStep>
steps;
193 steps.reserve(w->move_steps().size());
194 for (
const auto& s : w->move_steps()) {
195 steps.push_back({ .position = s.position, .delay_seconds = s.delay_seconds });
197 WiringRecord rec { .kind =
"move_to", .steps = std::move(
steps) };
198 if (w->times_count() > 1) {
199 rec.times = w->times_count();
203 if (w->interval().has_value()) {
204 WiringRecord rec { .kind =
"every", .interval = *w->interval() };
205 if (w->duration().has_value()) {
206 rec.duration = *w->duration();
208 if (w->times_count() > 1) {
209 rec.times = w->times_count();
213 return { .kind =
"commit_driven" };
216 void fill_wiring_pixels(
const Fabric& fabric, uint32_t
id,
float& trigger_out,
float& time_out)
218 const Wiring* w = fabric.wiring_for(
id);
223 if (!w->move_steps().empty()) {
225 }
else if (w->interval().has_value()) {
227 if (w->duration().has_value())
241 struct InternalRecord {
248 std::optional<glm::vec3>
color;
249 std::optional<float>
size;
253 float entity_type_norm { 0.0F };
254 float trigger_kind { 0.0F };
255 float time_kind { 0.0F };
257 uint32_t sink_type { 0 };
258 uint32_t first_audio_channel { 0 };
261 std::vector<InternalRecord> records;
263 for (uint32_t
id : fabric.
all_ids()) {
264 const auto k = fabric.
kind(
id);
268 if (!e || !e->position()) {
271 if (e->fn_name().empty()) {
273 "StateEncoder: Emitter {} has no fn_name",
id);
275 auto& rec = records.emplace_back(InternalRecord {
278 .position = *e->position(),
280 .radius = e->radius(),
283 .influence_fn_name = e->fn_name(),
284 .entity_type_norm = 0.0F,
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;
295 if (!s || !s->position()) {
298 if (s->fn_name().empty()) {
300 "StateEncoder: Sensor {} has no fn_name",
id);
302 auto& rec = records.emplace_back(InternalRecord {
305 .position = *s->position(),
307 .perception_fn_name = s->fn_name(),
308 .entity_type_norm = 0.333F,
310 fill_wiring_pixels(fabric,
id, rec.trigger_kind, rec.time_kind);
315 if (!
a || !
a->position()) {
318 if (
a->perception_fn_name().empty()) {
320 "StateEncoder: Agent {} has no perception_fn_name",
id);
322 if (
a->influence_fn_name().empty()) {
324 "StateEncoder: Agent {} has no influence_fn_name",
id);
326 auto& rec = records.emplace_back(InternalRecord {
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,
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;
349 if (records.empty()) {
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;
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);
370 expand_range(rs.intensity, rec.intensity, init_intensity);
371 expand_range(rs.radius, rec.radius, init_radius);
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);
378 expand_range(rs.size, *rec.size, init_size);
382 expand_range(rs.query_radius, rec.query_radius, init_query_radius);
389 const auto width =
static_cast<uint32_t
>(records.size());
393 image.height = k_exr_rows;
394 image.channels = k_channels;
397 std::vector<float>
pixels(
static_cast<size_t>(
width) * k_exr_rows * k_channels, 0.0F);
399 for (
size_t i = 0; i < records.size(); ++i) {
400 const auto& rec = records[i];
402 const size_t row0 = (
static_cast<size_t>(0) *
width + i) * k_channels;
408 const size_t row1 = (
static_cast<size_t>(1) *
width + i) * k_channels;
418 const size_t row2 = (
static_cast<size_t>(2) *
width + i) * k_channels;
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;
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);
438 const std::string exr_path = base_path +
".exr";
449 if (!writer->write(exr_path,
image, options)) {
450 m_last_error =
"EXR write failed: " + writer->get_last_error();
458 const char* kind_names[] = {
"emitter",
"sensor",
"agent" };
462 schema.fabric_name = fabric.
name();
464 schema.entities.reserve(records.size());
466 for (
const auto& rec : records) {
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;
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);
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 });
487 auto a = fabric.get_agent(rec.id);
489 ent.
audio_sinks.push_back({ s.channel, s.fn_name });
494 schema.entities.push_back(std::move(ent));
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();
506 "StateEncoder: wrote {} entities to {} + {}",
507 records.size(), exr_path, json_path);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
std::string perception_fn_name
std::vector< EntityRecord > entities
std::optional< double > duration
std::optional< double > interval
const std::vector< float > * pixels
std::optional< std::vector< WiringStep > > steps
std::optional< glm::vec3 > color
std::string influence_fn_name
std::optional< size_t > times
std::vector< RenderSinkRecord > render_sinks
std::vector< AudioSinkRecord > audio_sinks
std::unique_ptr< ImageWriter > create_writer(const std::string &filepath) const
static ImageWriterRegistry & instance()
std::shared_ptr< Sensor > get_sensor(uint32_t id) const
Get the Sensor registered under id.
std::vector< uint32_t > all_ids() const
List all registered entity ids in insertion order.
std::shared_ptr< Agent > get_agent(uint32_t id) const
Get the Agent registered under id.
const std::string & name() const
Assigned name, empty if the Fabric was constructed outside a Tapestry.
Kind kind(uint32_t id) const
Return the kind of entity registered under id.
std::shared_ptr< Emitter > get_emitter(uint32_t id) const
Get the Emitter registered under id.
Orchestrates spatial indexing and scheduling for Nexus objects.
bool encode(const Fabric &fabric, const std::string &base_path)
Encode the given Fabric to {base_path}.exr and {base_path}.json.
constexpr auto member(std::string_view key, T Class::*ptr)
constexpr auto opt_member(std::string_view key, std::optional< T > Class::*ptr)
@ 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)
Raw image data loaded from file.
std::vector< std::string > channel_names
Configuration for image writing.