Quality of life in procedural town creation improved

This commit is contained in:
2025-02-13 14:41:53 +03:00
parent b56103930c
commit 2eed8ef509
59 changed files with 8150 additions and 88 deletions

View File

@@ -1091,6 +1091,112 @@ class EdgeEditorHandler {
result = 1;
return result;
}
bool pack_buildings(
const AABB &lot_aabb,
std::vector<struct RoadLinesData::road_edge_side::buildings>
&buildings,
float gap)
{
int i;
Rect2 rect_lot(lot_aabb.position.x, lot_aabb.position.z,
lot_aabb.size.x, lot_aabb.size.z);
std::vector<Rect2> inputs, outputs;
List<Rect2> parts;
List<Rect2> parts_next;
rect_lot.grow_by(-4);
print_line("lot rect: " + (rect_lot.operator String()));
parts.push_back(rect_lot);
Vector<int> bad_indices;
for (i = 0; i < (int)buildings.size(); i++) {
if (buildings[i].id.length() == 0) {
if (bad_indices.find(i) < 0)
bad_indices.push_back(i);
continue;
}
const AABB &aabb_building =
BuildingsData::get_singleton()
->building_aabbs[buildings[i].id];
Transform building_rot(
Basis().rotated(
Vector3(0, 1, 0),
Math::deg2rad(buildings[i].y_rotation)),
Vector3());
const AABB &aabb_building_rot =
building_rot.xform(aabb_building);
Rect2 input(aabb_building_rot.position.x,
aabb_building_rot.position.z,
aabb_building_rot.size.x,
aabb_building_rot.size.z);
if (input.size.x < 0.1f) {
if (bad_indices.find(i) < 0)
bad_indices.push_back(i);
continue;
}
print_line("id: " + buildings[i].id +
" input: " + (input.operator String()));
input.grow_by(gap);
inputs.push_back(input);
}
bad_indices.invert();
for (i = 0; i < (int)bad_indices.size(); i++)
buildings.erase(buildings.begin() + bad_indices[i]);
if (buildings.size() < 2)
return false;
for (i = 0; i < (int)inputs.size(); i++) {
bool found = false;
List<Rect2>::Element *e = parts.front();
while (e) {
Rect2 part = e->get();
if (part.size.x >= inputs[i].size.x &&
part.size.y >= inputs[i].size.y) {
found = true;
Rect2 output(part.position.x,
part.position.y,
inputs[i].size.x,
inputs[i].size.y);
outputs.push_back(output);
Rect2 part1(part.position.x,
part.position.y +
inputs[i].size.y,
inputs[i].size.x,
part.size.y -
inputs[i].size.y);
Rect2 part2(part.position.x +
inputs[i].size.x,
part.position.y,
part.size.x -
inputs[i].size.x,
part.size.y);
parts_next.push_back(part1);
parts_next.push_back(part2);
break;
} else
parts_next.push_back(part);
e = e->next();
}
if (!found) {
print_line("can't fit " + itos(i));
break;
}
parts = parts_next;
}
if (outputs.size() < buildings.size())
return false;
Rect2 result;
for (i = 0; i < (int)outputs.size(); i++) {
print_line("output: " + itos(i) +
(outputs[i].operator String()));
result = result.merge(outputs[i]);
}
for (i = 0; i < (int)outputs.size(); i++) {
/* FIXME: why ? */
Vector2 center =
outputs[i].get_center() - result.get_center();
buildings[i].offsets.x = center.x;
buildings[i].offsets.z = center.y;
}
return true;
}
void event_handler(const String &event, const Vector<Variant> &args)
{
if (event == "road_lines_edge_editor::edit") {
@@ -1133,6 +1239,9 @@ class EdgeEditorHandler {
case 200:
pname = "clear";
break;
case 201:
pname = "clear-buildings";
break;
default:
pname = menu->get_item_metadata(item_index);
break;
@@ -1150,6 +1259,10 @@ class EdgeEditorHandler {
get_edge_conf<float>(
pname, "left",
"lot_y_rotation");
rl.edges[index].left.lot_offset =
get_edge_conf<float>(
pname, "left",
"lot_offset");
float dir_offt =
rl.points[index + 1]
.origin.distance_to(
@@ -1159,6 +1272,32 @@ class EdgeEditorHandler {
rl.edges[index].left.lot_dir_offset =
dir_offt;
}
} else if (pname.begins_with("residental-")) {
struct RoadLinesData::road_edge_side::buildings
b;
b.id = pname;
b.offsets = Vector3();
b.y_rotation = 0.0f;
rl.edges[index].left.buildings.push_back(b);
if (rl.edges[index].left.lot > 0) {
String lot_id =
rl.edges[index].left.lot_type;
if (rl.edges[index]
.left.buildings.size() >
1) {
const AABB &aabb_lot =
BuildingsData::get_singleton()
->building_aabbs
["lot-" +
lot_id];
pack_buildings(
aabb_lot,
rl.edges[index]
.left.buildings,
2.0f);
}
}
} else if (pname == "clear") {
rl.edges[index].left.lot_type = "";
rl.edges[index].left.lot = 0;
@@ -1175,6 +1314,9 @@ class EdgeEditorHandler {
case 200:
pname = "clear";
break;
case 201:
pname = "clear-buildings";
break;
default:
pname = menu->get_item_metadata(item_index);
break;
@@ -1195,6 +1337,10 @@ class EdgeEditorHandler {
get_edge_conf<float>(
pname, "right",
"lot_y_rotation");
rl.edges[index].right.lot_offset =
get_edge_conf<float>(
pname, "right",
"lot_offset");
float dir_offt =
rl.points[index + 1]
.origin.distance_to(
@@ -1204,6 +1350,33 @@ class EdgeEditorHandler {
rl.edges[index].right.lot_dir_offset =
dir_offt;
}
} else if (pname.begins_with("residental-") ||
pname.begins_with("business-")) {
struct RoadLinesData::road_edge_side::buildings
b;
b.id = pname;
b.offsets = Vector3();
b.y_rotation = 0.0f;
rl.edges[index].right.buildings.push_back(b);
if (rl.edges[index].right.lot > 0) {
String lot_id =
rl.edges[index].right.lot_type;
if (rl.edges[index]
.right.buildings.size() >
1) {
const AABB &aabb_lot =
BuildingsData::get_singleton()
->building_aabbs
["lot-" +
lot_id];
pack_buildings(
aabb_lot,
rl.edges[index]
.right.buildings,
2.0f);
}
}
} else if (pname == "clear") {
rl.edges[index].right.lot_type = "";
rl.edges[index].right.lot = 0;
@@ -1253,74 +1426,124 @@ public:
v->call_deferred("add_child", sep);
v->call_deferred("add_child", h2);
side_handler(v, right, "right");
MenuButton *mb = memnew(MenuButton);
v->call_deferred("add_child", mb);
mb->set_text("Action...");
MenuButton *mb_left = memnew(MenuButton);
v->call_deferred("add_child", mb_left);
mb_left->set_text("Select left lot...");
MenuButton *mb_right = memnew(MenuButton);
mb_right->set_text("Select right lot...");
v->call_deferred("add_child", mb_right);
Error err = stream_conf.load("res://config/stream.conf");
assert(err == OK);
struct menu_button_config {
String caption;
std::vector<std::pair<String, int> > *menu_items;
String filter;
String msg;
String event;
};
std::vector<std::pair<String, int> > menu_items = {
{ "Move lots starting from current edge to next edges",
100 },
};
std::vector<std::pair<String, int> > menu_items_leftright = {
{ "Clear current lot and building(s)", 200 },
{ "Clear current building(s)", 201 },
};
for (i = 0; i < (int)menu_items.size(); i++)
mb->get_popup()->add_item(menu_items[i].first,
menu_items[i].second);
for (i = 0; i < (int)menu_items_leftright.size(); i++) {
mb_left->get_popup()->add_item(
menu_items_leftright[i].first,
menu_items_leftright[i].second);
mb_right->get_popup()->add_item(
menu_items_leftright[i].first,
menu_items_leftright[i].second);
}
int item_id = 1000;
struct menu_button_config buttons[] = {
{
.caption = "Action...",
.menu_items = &menu_items,
.filter = "",
.event = event_prefix + "::menu",
},
{
.caption = "Select left lot...",
.menu_items = &menu_items_leftright,
.filter = "lot-",
.msg = "Create lot ",
.event = event_prefix + "::menu::left",
},
{
.caption = "Add left house...",
.menu_items = nullptr,
.filter = "residental-",
.msg = "Add house ",
.event = event_prefix + "::menu::left",
},
{
.caption = "Add left business...",
.menu_items = nullptr,
.filter = "business-",
.msg = "Add business ",
.event = event_prefix + "::menu::left",
},
{
.caption = "Select right lot...",
.menu_items = &menu_items_leftright,
.filter = "lot-",
.msg = "Create lot ",
.event = event_prefix + "::menu::right",
},
{
.caption = "Add right house...",
.menu_items = nullptr,
.filter = "residental-",
.msg = "Add house ",
.event = event_prefix + "::menu::right",
},
{
.caption = "Add right business...",
.menu_items = nullptr,
.filter = "business-",
.msg = "Add business ",
.event = event_prefix + "::menu::right",
},
};
Error err = stream_conf.load("res://config/stream.conf");
assert(err == OK);
Dictionary bdata =
stream_conf.get_value("buildings", "building_data");
int item_id = 1000;
List<Variant> building_list;
List<Variant>::Element *el;
bdata.get_key_list(&building_list);
el = building_list.front();
while (el) {
String pname = el->get();
String msg;
if (pname.begins_with("lot-"))
msg = "Create lot: " + pname;
else if (pname.begins_with("residental-"))
msg = "Add house: " + pname;
if (msg.length() > 0 && pname.begins_with("lot-")) {
std::vector<PopupMenu *> menus = {
mb_left->get_popup(),
mb_right->get_popup()
};
for (i = 0; i < (int)menus.size(); i++) {
menus[i]->add_item(msg, item_id);
int item_index =
menus[i]->get_item_index(
item_id);
menus[i]->set_item_metadata(item_index,
pname);
item_id++;
for (i = 0; i < (int)sizeof(buttons) / (int)sizeof(buttons[0]);
i++) {
int j;
MenuButton *mb = memnew(MenuButton);
v->call_deferred("add_child", mb);
mb->set_text(buttons[i].caption);
if (buttons[i].menu_items) {
for (j = 0;
j < (int)buttons[i].menu_items->size();
j++)
mb->get_popup()->add_item(
buttons[i]
.menu_items->data()[j]
.first,
buttons[i]
.menu_items->data()[j]
.second);
}
if (buttons[i].filter.length() > 0) {
List<Variant> building_list;
bdata.get_key_list(&building_list);
el = building_list.front();
while (el) {
String pname = el->get();
if (pname.begins_with(
buttons[i].filter)) {
String message =
buttons[i].msg + " " +
pname;
mb->get_popup()->add_item(
message, item_id);
int item_index =
mb->get_popup()
->get_item_index(
item_id);
mb->get_popup()
->set_item_metadata(
item_index,
pname);
item_id++;
}
el = el->next();
}
}
el = el->next();
menu_handlers.push_back(memnew(PopupMenuSelectHandler(
mb->get_popup(), buttons[i].event)));
}
menu_handlers.push_back(memnew(PopupMenuSelectHandler(
mb->get_popup(), event_prefix + "::menu")));
menu_handlers.push_back(memnew(PopupMenuSelectHandler(
mb_left->get_popup(), event_prefix + "::menu::left")));
menu_handlers.push_back(memnew(PopupMenuSelectHandler(
mb_right->get_popup(),
event_prefix + "::menu::right")));
EditorEvent::get_singleton()->event.add_listener(
this, &EdgeEditorHandler::event_handler);
}