98 struct InternalRecord {
101 glm::vec3 position {};
102 float intensity { 0.0F };
103 float radius { 0.0F };
104 float query_radius { 0.0F };
105 std::optional<glm::vec3> color;
106 std::optional<float> size;
107 std::string influence_fn_name;
108 std::string perception_fn_name;
110 float entity_type_norm { 0.0F };
111 float trigger_kind { 0.0F };
112 float time_kind { 0.0F };
114 uint32_t sink_type { 0 };
115 uint32_t first_audio_channel { 0 };
118 std::vector<InternalRecord> records;
120 for (uint32_t
id : fabric.
all_ids()) {
121 const auto k = fabric.
kind(
id);
125 if (!e || !e->position()) {
128 if (e->fn_name().empty()) {
130 "StateEncoder: Emitter {} has no fn_name",
id);
132 auto& rec = records.emplace_back(InternalRecord {
135 .position = *e->position(),
136 .intensity = e->intensity(),
137 .radius = e->radius(),
140 .influence_fn_name = e->fn_name(),
141 .entity_type_norm = 0.0F,
143 fill_wiring_pixels(fabric,
id, rec.trigger_kind, rec.time_kind);
144 rec.sink_type = (e->audio_sinks().empty() ? 0U : 1U)
145 | (e->render_sinks().empty() ? 0U : 2U);
146 if (!e->audio_sinks().empty())
147 rec.first_audio_channel = e->audio_sinks().front().channel;
152 if (!s || !s->position()) {
155 if (s->fn_name().empty()) {
157 "StateEncoder: Sensor {} has no fn_name",
id);
159 auto& rec = records.emplace_back(InternalRecord {
162 .position = *s->position(),
163 .query_radius = s->query_radius(),
164 .perception_fn_name = s->fn_name(),
165 .entity_type_norm = 0.333F,
167 fill_wiring_pixels(fabric,
id, rec.trigger_kind, rec.time_kind);
172 if (!
a || !
a->position()) {
175 if (
a->perception_fn_name().empty()) {
177 "StateEncoder: Agent {} has no perception_fn_name",
id);
179 if (
a->influence_fn_name().empty()) {
181 "StateEncoder: Agent {} has no influence_fn_name",
id);
183 auto& rec = records.emplace_back(InternalRecord {
186 .position = *
a->position(),
187 .intensity =
a->intensity(),
188 .radius =
a->radius(),
189 .query_radius =
a->query_radius(),
192 .influence_fn_name =
a->influence_fn_name(),
193 .perception_fn_name =
a->perception_fn_name(),
194 .entity_type_norm = 0.667F,
196 fill_wiring_pixels(fabric,
id, rec.trigger_kind, rec.time_kind);
197 rec.sink_type = (
a->audio_sinks().empty() ? 0U : 1U)
198 | (
a->render_sinks().empty() ? 0U : 2U);
199 if (!
a->audio_sinks().empty())
200 rec.first_audio_channel =
a->audio_sinks().front().channel;
206 if (records.empty()) {
216 bool init_pos_x =
false, init_pos_y =
false, init_pos_z =
false;
217 bool init_intensity =
false, init_radius =
false, init_query_radius =
false;
218 bool init_color_r =
false, init_color_g =
false, init_color_b =
false;
219 bool init_size =
false;
221 for (
const auto& rec : records) {
222 expand_range(rs.
pos_x, rec.position.x, init_pos_x);
223 expand_range(rs.
pos_y, rec.position.y, init_pos_y);
224 expand_range(rs.
pos_z, rec.position.z, init_pos_z);
227 expand_range(rs.
intensity, rec.intensity, init_intensity);
228 expand_range(rs.
radius, rec.radius, init_radius);
230 expand_range(rs.
color_r, rec.color->r, init_color_r);
231 expand_range(rs.
color_g, rec.color->g, init_color_g);
232 expand_range(rs.
color_b, rec.color->b, init_color_b);
235 expand_range(rs.
size, *rec.size, init_size);
239 expand_range(rs.
query_radius, rec.query_radius, init_query_radius);
246 const auto width =
static_cast<uint32_t
>(records.size());
256 for (
size_t i = 0; i < records.size(); ++i) {
257 const auto& rec = records[i];
280 pixels[row3 + 0] =
static_cast<float>(rec.id);
281 pixels[row3 + 1] = rec.entity_type_norm;
282 pixels[row3 + 2] = rec.trigger_kind;
283 pixels[row3 + 3] = rec.time_kind;
286 pixels[row4 + 0] =
static_cast<float>(rec.sink_type);
287 pixels[row4 + 1] =
static_cast<float>(rec.first_audio_channel);
295 const std::string exr_path = base_path +
".exr";
306 if (!writer->write(exr_path,
image, options)) {
307 m_last_error =
"EXR write failed: " + writer->get_last_error();
319 schema.
entities.reserve(records.size());
321 for (
const auto& rec : records) {
329 ent.
color = rec.color;
333 ent.
wiring = build_wiring(fabric, rec.
id);
337 for (
const auto& s : e->audio_sinks())
338 ent.
audio_sinks.push_back({ .channel = s.channel, .fn_name = s.fn_name });
339 for (
const auto& s : e->render_sinks())
342 auto a = fabric.get_agent(rec.id);
343 for (
const auto& s :
a->audio_sinks())
344 ent.audio_sinks.push_back({ .channel = s.channel, .fn_name = s.fn_name });
346 for (
const auto& s :
a->render_sinks())
347 ent.render_sinks.push_back({ .fn_name = s.fn_name });
349 if (
auto locus = std::dynamic_pointer_cast<Locus>(
a)) {
350 ent.subkind =
"locus";
351 const auto& nav = locus->nav();
352 ent.locus_nav = State::LocusNavRecord {
354 .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) },
355 .up = { 0.0F, 1.0F, 0.0F },
356 .fov = nav.fov_radians,
357 .near_plane = nav.near_plane,
358 .far_plane = nav.far_plane,
359 .speed = nav.move_speed,
361 }
else if (
auto presence = std::dynamic_pointer_cast<Presence>(
a)) {
362 ent.subkind =
"presence";
363 ent.radiate_fn_name = presence->radiate_fn_name();
364 ent.falloff_radius = presence->falloff_radius() != presence->query_radius()
365 ? std::optional<float>(presence->falloff_radius())
367 if (
auto fc = presence->falloff_curve())
372 schema.entities.push_back(std::move(ent));
375 for (uint32_t xid : fabric.all_expanse_ids()) {
376 const auto x = fabric.get_expanse(xid);
379 if (x->fn_name().empty()) {
381 "StateEncoder: Expanse {} has no fn_name, skipping", xid);
384 schema.expanses.push_back(State::ExpanseRecord {
386 .fn_name = x->fn_name(),
387 .on_enter_fn_name = x->on_enter_fn_name(),
388 .on_exit_fn_name = x->on_exit_fn_name(),
392 IO::JSONSerializer ser;
393 const std::string json_path = base_path +
".json";
394 if (!ser.write(json_path, schema)) {
395 m_last_error =
"Failed to write schema: " + ser.last_error();
401 "StateEncoder: wrote {} entities to {} + {}",
402 records.size(), exr_path, json_path);
407bool StateEncoder::encode(
const Tapestry& tapestry,
const std::string& base_dir, nlohmann::json user_state)
409 m_last_error.clear();
413 for (
const auto& fabric : tapestry.
all_fabrics()) {
414 const std::string fabric_id = fabric->name().empty()
415 ? std::to_string(fabric->id())
418 const std::string base_path = base_dir +
"/" + fabric_id;
420 if (!encode(*fabric, base_path)) {
426 .base_path = base_path,
430 for (
const auto& [xname, xptr] : tapestry.
all_expanses()) {
433 .fn_name = xptr->fn_name(),
434 .on_enter_fn_name = xptr->on_enter_fn_name(),
435 .on_exit_fn_name = xptr->on_exit_fn_name(),
437 for (
const auto& fabric : tapestry.
all_fabrics()) {
438 for (uint32_t xid : fabric->all_expanse_ids()) {
439 if (fabric->get_expanse(xid) == xptr) {
440 const std::string fname = fabric->name().empty()
441 ? std::to_string(fabric->id())
443 xrec.fabric_names.push_back(fname);
448 schema.
expanses.push_back(std::move(xrec));
454 const std::string tapestry_path = base_dir +
"/tapestry.json";
455 if (!ser.
write(tapestry_path, schema)) {
456 m_last_error =
"Failed to write tapestry schema: " + ser.
last_error();
462 "StateEncoder: wrote {} fabrics to {}", schema.
fabrics.size(), tapestry_path);