Files
streaming_world/src/modules/stream/buildings/element_data.cpp
2024-10-24 04:48:36 +03:00

842 lines
23 KiB
C++

#undef NDEBUG
#include <cassert>
#include "element_data.h"
ElementData *ElementData::singleton = nullptr;
const String &ElementData::get_element_type(const String &key) const
{
assert(elements.has(key));
return elements[key].type;
}
List<int> ElementData::get_grow_cells_side(const String &key, bool exterior,
int fl, int cell) const
{
List<int> grow_cells;
int x = cell % grid_size;
int z = cell / grid_size;
bool grow_west = true, grow_south = true, grow_east = true,
grow_north = true;
int rotation = get_grid_rotation(key, exterior, fl, cell);
if (x == 0)
grow_west = false;
else if (x >= grid_size - 1)
grow_east = false;
if (z == 0)
grow_south = false;
else if (z >= grid_size - 1)
grow_north = false;
if (rotation == 0 || rotation == 2) {
grow_south = false;
grow_north = false;
} else if (rotation == 1 || rotation == 3) {
grow_west = false;
grow_east = false;
}
if (grow_west) {
int cell_ = (x - 1) + z * grid_size;
grow_cells.push_back(cell_);
}
if (grow_west && grow_south) {
int cell_ = (x - 1) + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_south) {
int cell_ = x + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_south && grow_east) {
int cell_ = (x + 1) + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_east) {
int cell_ = (x + 1) + z * grid_size;
grow_cells.push_back(cell_);
}
if (grow_east && grow_north) {
int cell_ = (x + 1) + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_north) {
int cell_ = x + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_north && grow_west) {
int cell_ = (x - 1) + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
return grow_cells;
}
List<int> ElementData::get_grow_cells_normal(const String &key, bool exterior,
int fl, int cell) const
{
List<int> grow_cells;
int x = cell % grid_size;
int z = cell / grid_size;
bool grow_west = true, grow_south = true, grow_east = true,
grow_north = true;
if (x == 0)
grow_west = false;
else if (x >= grid_size - 1)
grow_east = false;
if (z == 0)
grow_south = false;
else if (z >= grid_size - 1)
grow_north = false;
if (grow_west) {
int cell_ = (x - 1) + z * grid_size;
grow_cells.push_back(cell_);
}
if (grow_west && grow_south) {
int cell_ = (x - 1) + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_south) {
int cell_ = x + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_south && grow_east) {
int cell_ = (x + 1) + (z - 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_east) {
int cell_ = (x + 1) + z * grid_size;
grow_cells.push_back(cell_);
}
if (grow_east && grow_north) {
int cell_ = (x + 1) + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_north) {
int cell_ = x + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
if (grow_north && grow_west) {
int cell_ = (x - 1) + (z + 1) * grid_size;
grow_cells.push_back(cell_);
}
return grow_cells;
}
List<int> ElementData::get_grow_cells(int type, const String &key,
bool exterior, int fl, int cell) const
{
switch (type) {
case 0:
return get_grow_cells_normal(key, exterior, fl, cell);
break;
case 1:
return get_grow_cells_side(key, exterior, fl, cell);
break;
default:
return List<int>();
break;
}
}
void ElementData::grow_cell(int type, const String &key, bool exterior, int fl,
int cell)
{
List<int> queue;
List<int> input_cells;
List<int> output_cells;
const String &element = get_grid_element(key, exterior, fl, cell);
print_line("grow_cell: " + element);
if (element == "empty")
return;
int rotation = get_grid_rotation(key, exterior, fl, cell);
queue.push_back(cell);
// collect all the same elements adjacent to current one
while (!queue.empty()) {
int c = queue.front()->get();
queue.pop_front();
const String &el = get_grid_element(key, exterior, fl, c);
int rot = get_grid_rotation(key, exterior, fl, c);
if (el == element && rot == rotation) {
print_line("adding cell: " + itos(c));
// do not place original cell in inputs
if (input_cells.find(c) == nullptr)
input_cells.push_back(c);
List<int> cells =
get_grow_cells(type, key, exterior, fl, c);
while (!cells.empty()) {
int item = cells.front()->get();
cells.pop_front();
if (input_cells.find(item) == nullptr &&
queue.find(item) == nullptr)
queue.push_back(item);
}
}
}
print_line("input_cells: " + itos(input_cells.size()));
queue = input_cells;
while (!queue.empty()) {
int c = queue.front()->get();
queue.pop_front();
const String &el = get_grid_element(key, exterior, fl, c);
if (el == element) {
List<int> cells =
get_grow_cells(type, key, exterior, fl, c);
while (!cells.empty()) {
int g = cells.front()->get();
cells.pop_front();
const String &em =
get_grid_element(key, exterior, fl, g);
if (em == "empty" && queue.find(g) == nullptr)
queue.push_back(g);
}
} else if (el == "empty") {
if (output_cells.find(c) == nullptr)
output_cells.push_back(c);
}
}
queue = output_cells;
while (!queue.empty()) {
int c = queue.front()->get();
queue.pop_front();
const String &cell_element =
get_grid_element(key, exterior, fl, c);
assert(cell_element == "empty");
if (cell_element == "empty") {
set_grid_element(key, exterior, fl, c, element);
set_grid_rotation(key, exterior, fl, c, rotation);
}
}
print_line("initial cell: " + itos(cell));
print_line("input_cells: " + itos(input_cells.size()));
print_line("output_cells: " + itos(output_cells.size()));
}
void ElementData::create_new_layout(const String &key)
{
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity top = ecs.lookup("grid_layouts");
assert(top.is_valid());
flecs::entity layout = ecs.entity(key.ascii().ptr()).child_of(top);
// one floor by default
layout.add<struct grid_layout>();
flecs::entity extr = ecs.entity("exterior").child_of(layout);
extr.add<grid_layout_exterior>();
flecs::entity intr = ecs.entity("interior").child_of(layout);
intr.add<grid_layout_interior>();
intr.set<grid_layout_base>({ 0 });
extr.set<grid_layout_base>({ 0 });
}
void ElementData::create_new_exterior_floor(const String &key)
{
int i;
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity ext = get_base(key, true);
assert(ext.is_valid());
struct grid_layout_base *l = ext.get_mut<grid_layout_base>();
int floor = l->floor_count;
flecs::entity fl =
ecs.entity(("floor_" + itos(floor)).ascii().ptr()).child_of(ext);
assert(fl.is_valid());
for (i = 0; i < grid_size * grid_size; i++) {
flecs::entity item =
ecs.entity(("item_" + itos(i)).ascii().ptr())
.child_of(fl);
item.set<grid_floor_item>({ i, "empty", 0 });
}
l->floor_count++;
}
void ElementData::create_new_interior_floor(const String &key)
{
int i;
flecs::entity intr = get_base(key, false);
assert(intr.is_valid());
struct grid_layout_base *l = intr.get_mut<grid_layout_base>();
int floor = l->floor_count;
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity fl = ecs.entity(("floor_" + itos(floor)).ascii().ptr())
.child_of(intr);
assert(fl.is_valid());
for (i = 0; i < grid_size * grid_size; i++) {
flecs::entity item =
ecs.entity(("item_" + itos(i)).ascii().ptr())
.child_of(fl);
item.set<grid_floor_item>({ i, "empty", 0 });
}
l->floor_count++;
}
void ElementData::serialize_layouts(Dictionary &store)
{
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity top = ecs.lookup("grid_layouts");
assert(top.is_valid());
top.children([this, &store](flecs::entity l) {
Dictionary layout, exterior_layout, interior_layout;
if (l.has<struct grid_layout>()) {
flecs::entity intr = l.lookup("interior");
assert(intr.is_valid());
flecs::entity extr = l.lookup("exterior");
assert(extr.is_valid());
intr.children([this, &interior_layout](
flecs::entity intr_fl) {
if (intr_fl.has<struct grid_floor>()) {
Array items;
intr_fl.children([&items](
flecs::entity
floor_item) {
if (floor_item.has<
struct grid_floor_item>()) {
const struct grid_floor_item *item =
floor_item.get<
struct grid_floor_item>();
Dictionary sitem;
sitem["index"] =
item->index;
sitem["element"] =
item->element;
sitem["rotation"] =
item->rotation;
items.push_back(sitem);
}
});
String floor_key(intr_fl.name());
interior_layout[floor_key] = items;
}
});
extr.children([this, &exterior_layout](
flecs::entity extr_fl) {
Array items;
extr_fl.children([&items](flecs::entity
floor_item) {
if (floor_item.has<
struct grid_floor_item>()) {
const struct grid_floor_item
*item = floor_item.get<
struct grid_floor_item>();
Dictionary sitem;
sitem["index"] = item->index;
sitem["element"] =
item->element;
sitem["rotation"] =
item->rotation;
items.push_back(sitem);
}
});
String floor_key(extr_fl.name());
exterior_layout[floor_key] = items;
});
layout["interior"] = interior_layout;
layout["exterior"] = exterior_layout;
}
String layout_name(l.name());
store[layout_name] = layout;
});
}
void ElementData::unserialize_layouts(const Dictionary &store)
{
int i;
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity top = ecs.lookup("grid_layouts");
assert(top.is_valid());
// delete all layouts
top.children([this](flecs::entity l) { l.destruct(); });
List<Variant> layout_keys;
store.get_key_list(&layout_keys);
List<Variant>::Element *e;
e = layout_keys.front();
while (e) {
String layout_name = e->get();
flecs::entity layout =
ecs.entity(layout_name.ascii().ptr()).child_of(top);
layout.add<grid_layout>();
flecs::entity extr = ecs.entity("exterior").child_of(layout);
extr.add<grid_layout_exterior>();
extr.set<grid_layout_base>({ 0 });
flecs::entity intr = ecs.entity("interior").child_of(layout);
intr.add<grid_layout_interior>();
intr.set<grid_layout_base>({ 0 });
Dictionary store_layout = store[e->get()];
Dictionary store_interior = store_layout["interior"];
Dictionary store_exterior = store_layout["exterior"];
List<Variant>::Element *ve;
List<Variant> interior_keys;
List<Variant> exterior_keys;
store_interior.get_key_list(&interior_keys);
store_exterior.get_key_list(&exterior_keys);
for (ve = interior_keys.front(); ve; ve = ve->next()) {
String floor_key = ve->get();
if (floor_key.begins_with("floor_")) {
flecs::entity floor_e =
ecs.entity(floor_key.ascii().ptr())
.child_of(intr);
assert(floor_e.is_valid());
floor_e.set<struct grid_floor>({ true });
const Array &floor_interior =
store_interior[floor_key];
for (i = 0; i < floor_interior.size(); i++) {
const Dictionary &item =
floor_interior[i];
int index = item["index"];
String element = item["element"];
int rotation = item["rotation"];
String item_key = "item_" + itos(index);
flecs::entity item_e =
ecs.entity(item_key.ascii()
.ptr())
.child_of(floor_e);
item_e.set<grid_floor_item>(
{ index, element, rotation });
}
struct grid_layout_base *l =
intr.get_mut<struct grid_layout_base>();
l->floor_count++;
}
}
for (ve = exterior_keys.front(); ve; ve = ve->next()) {
String floor_key = ve->get();
if (floor_key.begins_with("floor_")) {
flecs::entity floor_e =
ecs.entity(floor_key.ascii().ptr())
.child_of(extr);
assert(floor_e.is_valid());
floor_e.add<struct grid_floor>();
const Array &floor_exterior =
store_exterior[floor_key];
for (i = 0; i < floor_exterior.size(); i++) {
const Dictionary &item =
floor_exterior[i];
int index = item["index"];
String element = item["element"];
int rotation = item["rotation"];
String item_key = "item_" + itos(index);
flecs::entity item_e =
ecs.entity(item_key.ascii()
.ptr())
.child_of(floor_e);
item_e.set<grid_floor_item>(
{ index, element, rotation });
}
struct grid_layout_base *l =
extr.get_mut<struct grid_layout_base>();
l->floor_count++;
}
}
e = e->next();
}
}
void ElementData::ensure_floor(const String &key, bool exterior, int fl)
{
int i;
if (has_floor(key, exterior, fl))
return;
flecs::world ecs = BaseData::get_singleton()->get();
flecs::entity base = get_base(key, exterior);
flecs::entity floor_e =
ecs.entity(("floor_" + itos(fl)).ascii().ptr()).child_of(base);
assert(floor_e.is_valid());
if (!floor_e.has<struct grid_floor>())
floor_e.set<struct grid_floor>({ true });
for (i = 0; i < grid_size * grid_size; i++) {
flecs::entity item =
ecs.entity(("item_" + itos(i)).ascii().ptr())
.child_of(floor_e);
item.set<grid_floor_item>({ i, "empty", 0 });
}
assert(has_floor(key, exterior, fl));
print_line("ensured floor: " + itos(fl));
}
void ElementData::load_data()
{
int i;
ConfigFile config;
ConfigFile automata_conf;
Dictionary conf_element_types;
Dictionary conf_elements;
Dictionary conf_grid_layouts;
List<Variant> keys;
List<Variant>::Element *e;
elements.clear();
element_type.clear();
config.load("res://astream/blayout.conf");
conf_element_types = config.get_value("buildings_layout",
"element_types", Dictionary());
conf_elements =
config.get_value("buildings_layout", "elements", Dictionary());
conf_grid_layouts = config.get_value("buildings_layout", "grid_layouts",
Dictionary());
conf_element_types.get_key_list(&keys);
e = keys.front();
while (e) {
String key = e->get();
Dictionary item = conf_element_types[key];
assert(item.has("sockets"));
create_element_type(key);
Array sockets = item["sockets"];
for (i = 0; i < sockets.size(); i++)
set_element_type_socket(key, i, sockets[i]);
e = e->next();
}
keys.clear();
conf_elements.get_key_list(&keys);
e = keys.front();
while (e) {
String key = e->get();
Dictionary item = conf_elements[key];
assert(item.has("type"));
assert(item.has("mesh_names"));
String type = item["type"];
String base = item.get("base", "");
print_line("loading element: " + key + " type: " + type);
if (key == "empty") {
e = e->next();
continue;
}
create_element(key, type);
Array mesh_names = item["mesh_names"];
for (i = 0; i < mesh_names.size(); i++)
set_element_mesh_name(key, i, mesh_names[i]);
set_element_base(key, base);
e = e->next();
}
unserialize_layouts(conf_grid_layouts);
{
Error err = automata_conf.load("res://astream/automata.conf");
assert(err == OK);
Dictionary automata_dict = automata_conf.get_value(
"automata", "automata", Dictionary());
assert(automata_dict.size() > 0);
List<Variant> pkeys;
List<Variant>::Element *pe;
automata_dict.get_key_list(&pkeys);
pe = pkeys.front();
automata.clear();
while (pe) {
String name = pe->get();
print_line("automata: " + name);
assert(automata_dict.has(name));
Array at = automata_dict[name];
assert(at.size() > 0);
for (i = 0; i < at.size(); i++)
add_automata_from_dict(name, at[i]);
assert(automata.has(name));
pe = pe->next();
}
}
assert(automata.size() > 0);
EditorEvent::get_singleton()->event.emit("elements_update_all",
varray());
}
void ElementData::add_automata(const String &name,
const struct ElementData::match_field *match,
const struct ElementData::match_field &cell)
{
struct ElementData::automata_data data;
int i;
for (i = 0; i < 9; i++)
data.match[i] = match[i];
data.cell = cell;
automata[name].push_back(data);
}
void ElementData::add_automata_from_dict(const String &name,
const Dictionary &d)
{
int i;
assert(d.has("match"));
assert(d.has("cell"));
Array cell = d["cell"];
Array matches = d["match"];
assert(matches.size() == 9);
if (cell.size() == 2) {
struct ElementData::automata_data data;
for (i = 0; i < matches.size(); i++) {
Array match = matches[i];
assert(match.size() == 2);
String entry = match[0];
int rotation = match[1];
data.match[i] = { entry, rotation };
}
String n = cell[0];
int r = cell[1];
data.cell = { n, r };
if (automata.has(name))
automata[name].push_back(data);
else {
Vector<struct ElementData::automata_data> l;
l.push_back(data);
automata[name] = l;
}
} else if (cell.size() == 5) {
/*******************************************
* 0 1 2
* 3 4 5 0
* 6 7 8
*
* 2 5 8
* 1 4 7 90
* 0 3 6
*
* 8 7 6
* 5 4 3 180
* 2 1 0
*
* 6 3 0
* 7 4 1 270
* 8 5 2
*/
/* clang-format off */
int remap[9][9] = {
{
/* 0 */
0, 1, 2, /* a */
3, 4, 5, /* b */
6, 7, 8, /* c */
},
{
/* 90 */
2, 5, 8,
1, 4, 7,
0, 3, 6,
},
{
/* 180 */
8, 7, 6,
5, 4, 3,
2, 1, 0,
},
{
/* 270 */
6, 3, 0,
7, 4, 1,
8, 5, 2,
},
};
/* clang-format on */
int j;
struct ElementData::automata_data data[4];
for (j = 0; j < 4; j++) {
for (i = 0; i < matches.size(); i++) {
Array match = matches[remap[j][i]];
assert(match.size() == 2);
String entry = match[0];
int rotation = match[1];
if (rotation >= 0)
rotation = (rotation + j) % 4;
data[j].match[i] = { entry, rotation };
String n = cell[0];
int r = cell[1 + j];
data[j].cell = { n, r };
}
}
if (!automata.has(name)) {
Vector<struct ElementData::automata_data> l;
automata[name] = l;
}
for (j = 0; j < 4; j++)
automata[name].push_back(data[j]);
}
}
void ElementData::run_cellular_automata(const String &name, const String &key,
bool exterior, int fl)
{
int i, j, k;
print_line("automata: " + name);
assert(automata.size() > 0);
assert(automata.has(name));
flecs::entity base = get_base(key, exterior);
flecs::entity floor_e =
base.lookup(("floor_" + itos(fl)).ascii().ptr());
assert(floor_e.is_valid());
bool changed = true;
while (changed) {
changed = false;
for (k = 0; k < automata[name].size(); k++) {
for (i = 0; i < grid_size * grid_size; i++) {
flecs::entity item = floor_e.lookup(
("item_" + itos(i)).ascii().ptr());
const struct grid_floor_item *floor_item =
item.get<struct grid_floor_item>();
int index = floor_item->index;
// print_line("automata: processing: " + itos(index));
assert(index == i);
int neighbor_indices[9] = {
/* a */ index - grid_size - 1,
index - grid_size,
index - grid_size + 1,
/* b */ index - 1,
index,
index + 1,
/* c */ index + grid_size - 1,
index + grid_size,
index + grid_size + 1
};
struct ElementData::match_field match[9];
for (j = 0; j < 9; j++) {
int idx = neighbor_indices[j];
if (idx < 0 ||
idx >= grid_size * grid_size) {
match[j].entry = "empty";
match[j].rotation = -1;
} else {
flecs::entity mitem =
floor_e.lookup(
("item_" +
itos(idx))
.ascii()
.ptr());
const struct ElementData::grid_floor_item
*fl_item = mitem.get<
struct ElementData::
grid_floor_item>();
match[j].entry =
fl_item->element;
match[j].rotation =
fl_item->rotation;
}
// print_line("automata: processing: " +
// itos(index) + " " + match[j].entry +
// " " + itos(match[j].rotation));
}
// print_line("match created: " +
// itos(automata[name].size()));
bool matched = true;
for (j = 0; j < 9; j++) {
if (match[j].entry !=
automata[name][k].match[j].entry) {
matched = false;
break;
}
if (automata[name][k].match[j].rotation >=
0 &&
match[j].rotation >= 0 &&
match[j].rotation !=
automata[name][k]
.match[j]
.rotation) {
matched = false;
break;
}
}
if (matched) {
for (j = 0; j < 9; j++) {
print_line(
"matched: " +
match[j].entry + " " +
itos(match[j].rotation) +
" => " +
automata[name][k]
.match[j]
.entry +
" " +
itos(automata[name][k]
.match[j]
.rotation));
}
}
if (matched) {
struct grid_floor_item *floor_item_rw =
item.get_mut<
struct grid_floor_item>();
assert(has_element(
automata[name][k].cell.entry));
floor_item_rw->element =
automata[name][k].cell.entry;
changed = true;
print_line("matched, " +
floor_item->element);
if (automata[name][k].cell.rotation >=
0)
floor_item_rw->rotation =
automata[name][k]
.cell.rotation;
}
}
}
}
}
void ElementData::save_data()
{
int i;
ConfigFile config;
Dictionary conf_element_types;
Dictionary conf_elements;
Dictionary conf_exterior_grid;
List<String> keys;
List<String>::Element *e;
element_type.get_key_list(&keys);
e = keys.front();
while (e) {
Dictionary item;
const struct grid_element_type &g = element_type[e->get()];
if (e->get() == "empty") {
e = e->next();
continue;
}
item["name"] = e->get();
Array sockets;
sockets.resize(ELEMENT_SOCKETS);
for (i = 0; i < ELEMENT_SOCKETS; i++)
sockets[i] = g.sockets[i];
item["sockets"] = sockets;
conf_element_types[e->get()] = item;
e = e->next();
}
keys.clear();
elements.get_key_list(&keys);
e = keys.front();
while (e) {
Dictionary item;
const struct grid_element &g = elements[e->get()];
if (e->get() == "empty") {
e = e->next();
continue;
}
item["name"] = e->get();
item["type"] = g.type;
Array mesh_names;
mesh_names.resize(ELEMENT_SOCKETS);
for (i = 0; i < ELEMENT_SOCKETS; i++)
mesh_names[i] = g.mesh_names[i];
item["mesh_names"] = mesh_names;
item["base"] = g.base;
conf_elements[e->get()] = item;
e = e->next();
}
// TODO: support multiple layouts;
serialize_layouts(conf_exterior_grid);
config.set_value("buildings_layout", "element_types",
conf_element_types);
config.set_value("buildings_layout", "elements", conf_elements);
config.set_value("buildings_layout", "grid_layouts",
conf_exterior_grid);
config.save("res://astream/blayout.conf");
}
Spatial *ElementData::get_grid_node(const String &key, bool exterior, int fl,
int i)
{
assert(has_floor(key, exterior, fl));
flecs::entity item_data = get_grid_entity(key, exterior, fl, i);
if (!item_data.has<struct grid_floor_item_node>()) {
Spatial *sp = memnew(Spatial);
get_as_node<Spatial>("%bg_floor")
->call_deferred("add_child", sp);
item_data.set<struct grid_floor_item_node>({ sp });
int x = i % grid_size;
int z = i / grid_size;
int rotation = get_grid_rotation(key, exterior, fl, i);
sp->set_transform(Transform(
Basis().rotated(Vector3(0, 1, 0),
Math_PI * rotation / 2.0f),
Vector3((x - 4) * 4, 1 + 5 * fl, (z - 4) * 4) -
get_as_node<Spatial>("%bg_floor")
->get_transform()
.origin));
} else {
int rotation = get_grid_rotation(key, exterior, fl, i);
const struct grid_floor_item_node *item =
item_data.get<struct grid_floor_item_node>();
Spatial *node = item->node;
assert(node);
Transform xform = node->get_transform();
xform.basis = Basis().rotated(Vector3(0, 1, 0),
rotation * Math_PI / 2.0f);
node->set_transform(xform);
}
return item_data.get<struct grid_floor_item_node>()->node;
}