#include #include #include #include #include "town_queue.h" #include "town.h" static inline AABB cut_left(AABB &aabb, float amount) { if (amount > aabb.size.x) return AABB(); AABB ret(aabb.position, Vector3(amount, aabb.size.y, aabb.size.z)); aabb.position.x += amount; aabb.size.x -= amount; return ret; } static inline AABB cut_ztop(AABB &aabb, float amount) { if (amount > aabb.size.z) return AABB(); AABB ret(aabb.position, Vector3(aabb.size.x, aabb.size.y, amount)); aabb.position.z += amount; aabb.size.z -= amount; return ret; } static inline Variant get_item_data(const Dictionary &item, const char *key) { const Dictionary &item_data = item["data"]; return item_data[key]; } static inline void set_item_data(Dictionary &item, const char *key, const Variant &value) { if (!item.has("data")) item["data"] = Dictionary(); Dictionary data = item["data"]; data[key] = value; } static inline void set_item_data(Dictionary &item, const StringName &key, const Variant &value) { if (!item.has("data")) item["data"] = Dictionary(); Dictionary data = item["data"]; data[key] = value; } static inline String get_item_name(const Dictionary &item) { return item["name"]; } static inline void set_item_name(Dictionary &item, const StringName &value) { item["name"] = value; } static inline bool item_has_data(const Dictionary &item, const char *key) { const Dictionary &item_data = item["data"]; return item_data.has(key); } #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) Dictionary TownQueue::produce_item(const StringName &item_name, const Dictionary &parent, const Dictionary &extra) { Dictionary item; const char *copy_fields[] = { "set", "town", "patch", "lot", "wing" }; set_item_name(item, item_name); item["data"] = Dictionary(); if (!parent.empty()) { int j; const String &parent_name = parent["name"]; set_item_data(item, parent_name, parent); for (j = 0; j < (int)ARRAY_SIZE(copy_fields); j++) if (item_has_data(parent, copy_fields[j])) set_item_data(item, copy_fields[j], get_item_data(parent, copy_fields[j])); } const Variant *e; for (e = extra.next(NULL); e; e = extra.next(e)) set_item_data(item, *e, extra[*e]); return item; } void TownQueue::startup(const Dictionary &town_extra) { rnd.instance(); seed = -1; radius = 0.0f; center_radius = 0.0f; if (town_extra.has("seed")) seed = town_extra["seed"]; if (town_extra.has("radius")) radius = town_extra["radius"]; if (town_extra.has("center_radius")) center_radius = town_extra["center_radius"]; if (seed < 0) rnd->randomize(); seed = rnd->get_seed(); global_meta["seed"] = seed; global_meta["radius"] = radius; global_meta["center_radius"] = center_radius; global_meta["lots"] = Array(); global_meta["road_segments"] = Array(); global_meta["road_intersections"] = Array(); Dictionary town_item = produce_item_positional("town", Dictionary(), Vector3(), 0.0f, town_extra); queue.push_back(town_item); queue.register_callback_native("town", this, (void (Object::*)(const Dictionary&)) &TownQueue::handle_town); queue.register_callback_native("patch", this, (void (Object::*)(const Dictionary&)) &TownQueue::handle_patch); queue.register_callback_native("lot", this, (void (Object::*)(const Dictionary&)) &TownQueue::handle_lot); queue.register_callback_native("residence", this, (void (Object::*)(const Dictionary&)) &TownQueue::handle_residence); queue.register_callback_native("farm", this, (void (Object::*)(const Dictionary&)) &TownQueue::handle_farm); } void TownQueue::init_grid(Dictionary &item) { int i,j; int grid_x = get_item_data(item, "grid_x"); int grid_z = get_item_data(item, "grid_z"); Vector3 grid_origin = get_item_data(item, "grid_origin"); PoolVector grid = get_item_data(item, "grid"); PoolVector polygon = get_item_data(item, "polygon_rot"); Vector poly2d; poly2d.resize(polygon.size()); const Vector3 *pd = polygon.read().ptr(); for (i = 0; i < polygon.size(); i++) poly2d.write[i] = Vector2(pd[i].x, pd[i].z); uint8_t *gptr = grid.write().ptr(); for (i = 0; i < grid_z; i++) for (j = 0; j < grid_x; j++) { Vector3 grid_pos = grid_origin + Vector3((float)j * 16.0, 0.0f, (float)i * 16.0); if (Geometry::is_point_in_polygon(Vector2(grid_pos.x, grid_pos.z), poly2d)) gptr[i * grid_x + j] = 0; else gptr[i * grid_x + j] = 0xff; } set_item_data(item, "grid", grid); } void TownQueue::handle_town(const Dictionary &item) { Voronoi *voronoi = Voronoi::get_singleton(); int patches_count = 5 + rnd->randi() % 10, i; PoolVector points2d; points2d.resize(patches_count * 8); float sa = rnd->randf() * 2.0 * Math_PI; for (i = 0; i < patches_count * 8; i++) { float a = sa + sqrtf((float)i) * 5.0f; float r = (i == 0) ? 0.0f : MIN(center_radius + (float)i * 4.0f * rnd->randf(), radius); float x = cos(a) * r; float y = sin(a) * r; Vector2 d(x, y); points2d.write()[i] = d; } Dictionary diagram = voronoi->generate_diagram(points2d, relaxations); global_meta["voronoi"] = diagram; Array sites = diagram["sites"]; patches.resize(sites.size()); int plaza_id = -1; float plength = Math_INF; for (i = 0; i < sites.size(); i++) { Dictionary site = sites[i]; Vector polygon = site["polygon"]; Vector2 pos = site["pos"]; printf("site pos %ls\n", String(pos).c_str()); float l = pos.length_squared(); if (plength > l) { plaza_id = i; plength = l; } struct patch p(polygon, pos, patches.size()); patches.write[i] = p; } for (i = 0; i < sites.size(); i++) { Dictionary p_extra = patches[i].get(); if (i == plaza_id) p_extra["plaza"] = true; else p_extra["plaza"] = false; Dictionary patch_item = produce_item_positional("patch", item, p_extra["position"], 0.0f, p_extra); queue.push_back(patch_item); } } static inline float polygon_area(const PoolVector &polygon) { float area = 0.0f; int n = polygon.size(); const Vector3 *p = polygon.read().ptr(); for (int i = 0; i < n; i++) { int j = (i + 1) % n; area += 0.5f * (p[i].x*p[j].z - p[j].x*p[i].z); } return (area); } static inline bool get_grid_free(const Dictionary &item, int x, int z, int dx, int dz) { int grid_x = get_item_data(item, "grid_x"); int grid_z = get_item_data(item, "grid_z"); PoolVector grid = get_item_data(item, "grid"); const uint8_t *data = grid.read().ptr(); int minx = MAX(0, x); int maxx = MIN(x + dx, grid_x); int minz = MAX(0, z); int maxz = MIN(z + dz, grid_z); int i, j; if (x >= grid_x || z >= grid_z) return false; for (i = minz; i < maxz; i++) for (j = minx; j < maxx; j++) if (data[i * grid_x + j] != 0) return false; return true; } static inline bool get_grid_free(const Dictionary &item, const AABB &aabb) { Vector3 grid_origin = get_item_data(item, "grid_origin"); Vector3 offt = aabb.position - grid_origin; int x = (int)((offt.x + 8.0f) / 16.0f); int z = (int)((offt.x + 8.0f) / 16.0f); int dx = (int)((aabb.size.x + 8.0f) / 16.0f); int dz = (int)((aabb.size.z + 8.0f) / 16.0f); return get_grid_free(item, x, z, dx, dz); } static inline bool shrink_aabb(const Dictionary &item, AABB &aabb) { while (aabb.size.x > 16.0f && aabb.size.z > 16.0f && !get_grid_free(item, aabb)) { aabb.position.x += 1.0f; aabb.position.z += 1.0f; aabb.size.x -= 2.0f; aabb.size.z -= 2.0f; } if (aabb.size.x > 16.0f && aabb.size.z > 16.0f) return true; else return false; } void TownQueue::handle_patch(const Dictionary &item) { int i; Dictionary item_data = item["data"]; Dictionary p_extra; p_extra["plaza"] = item_data["plaza"]; float rotation = rnd->randf() * 2.0f * Math_PI; Transform xform = Transform().rotated(Vector3(0, 1, 0), rotation); PoolVector polygon = item_data["polygon"]; PoolVector polygon_rot; polygon_rot.resize(polygon.size()); Vector3 position = item_data["position"]; AABB aabb_rot(position, Vector3()), aabb(position, Vector3()); for (i = 0; i < polygon.size(); i++) { aabb.expand_to(polygon.read()[i]); Vector3 xv = xform.xform_inv(polygon.read()[i]); polygon_rot.write()[i] = xv; aabb_rot.expand_to(xv); } aabb.size.y = 3.0f; aabb_rot.size.y = 3.0f; AABB aabb_shrunk = aabb_rot; Vector3 grid_origin = aabb_rot.position; int grid_x = (int)(aabb_rot.size.x + 8.0f / 16.0f); int grid_z = (int)(aabb_rot.size.z + 8.0f / 16.0f); PoolVector grid; grid.resize(grid_x * grid_z); p_extra["polygon"] = polygon; p_extra["polygon_rot"] = polygon_rot; p_extra["aabb"] = aabb; p_extra["aabb_rot"] = aabb_rot; p_extra["grid_origin"] = grid_origin; p_extra["grid_x"] = grid_x; p_extra["grid_z"] = grid_z; p_extra["grid"] = grid; p_extra["area"] = polygon_area(polygon); Dictionary lot = produce_item_positional("lot", item, item_data["position"], rotation, p_extra); init_grid(lot); if (shrink_aabb(lot, aabb_shrunk)) set_item_data(lot, "aabb_rot_shrunk", aabb_shrunk); else set_item_data(lot, "aabb_rot_shrunk", AABB()); queue.push_back(lot); printf("grid_x %d grid_z %d\n", grid_x, grid_z); } void TownQueue::handle_lot(const Dictionary &item) { int grid_x = get_item_data(item, "grid_x"); int grid_z = get_item_data(item, "grid_z"); AABB aabb = get_item_data(item, "aabb_rot_shrunk"); Vector3 position = get_item_data(item, "position"); float rotation = get_item_data(item, "rotation"); HashMap item_selection; List key_list; item_selection[80] = "industry"; item_selection[60] = "farm"; item_selection[40] = "residence"; item_selection[20] = "recreation"; item_selection.get_key_list(&key_list); key_list.sort(); key_list.invert(); String item_name = "farm"; if (grid_x > 128 && grid_z > 128) item_name = "farm"; else if (grid_x > 64 && grid_z > 64) { List::Element *k = key_list.front(); int choice = rnd->randi() % 100; while(k) { int key = k->get(); if (choice > key) { item_name = item_selection[key]; break; } k = k->next(); } } else if (grid_x <= 64 && grid_z <= 64) item_name = "residence"; Dictionary next_item = produce_item_positional(item_name, item, position, rotation, item["data"]); set_item_data(next_item, "aabb", aabb); queue.push_back(next_item); } void TownQueue::handle_residence(const Dictionary &item) { Dictionary lot = get_item_data(item, "lot"); GenCitySet *city = Object::cast_to(get_item_data(item, "set")); const Array &buildings = city->get_building_sets(); ERR_FAIL_COND(buildings.size() == 0); int bset_id = rnd->randi() % buildings.size(); GenBuildingSet *bset = Object::cast_to(buildings[bset_id]); AABB aabb = get_item_data(item, "aabb"); if (aabb.size.x < 16.0f || aabb.size.z < 16.0f) /* we better produce hut in this case */ return; Dictionary p_extra; p_extra["bset"] = bset; p_extra["house_type"] = bset->get("house_type"); p_extra["placement"] = "arc"; p_extra["aabb"] = aabb; Dictionary house = produce_item_positional("house", item, get_item_data(item, "position"), /* need to remove this as lot is already rotated..., * but need this for house node */ get_item_data(item, "rotation"), p_extra); queue.push_back(house); } void TownQueue::handle_farm(const Dictionary &item) { AABB aabb = get_item_data(item, "aabb"); AABB field_aabb, farmtech_aabb, residence_aabb; if (aabb.size.x > aabb.size.z) field_aabb = cut_left(aabb, aabb.size.x * 0.5f); else field_aabb = cut_ztop(aabb, aabb.size.z * 0.5f); if (aabb.size.x > aabb.size.z) farmtech_aabb = cut_left(aabb, aabb.size.x * 0.5f); else farmtech_aabb = cut_ztop(aabb, aabb.size.z * 0.5f); residence_aabb = aabb; Vector3 field_position = field_aabb.position + Vector3(field_aabb.size.x, 0.0f, field_aabb.size.z) * 0.5f; Vector3 farmtech_position = farmtech_aabb.position + Vector3(farmtech_aabb.size.x, 0.0f, farmtech_aabb.size.z) * 0.5f; Vector3 residence_position = residence_aabb.position + Vector3(residence_aabb.size.x, 0.0f, residence_aabb.size.z) * 0.5f; Dictionary field_extra, farmtech_extra, residence_extra; field_extra["aabb"] = field_aabb; farmtech_extra["aabb"] = farmtech_aabb; residence_extra["aabb"] = residence_aabb; Dictionary field = produce_item_positional("field", item, field_position, 0.0f, field_extra); Dictionary farmtech = produce_item_positional("farmtech", item, farmtech_position, 0.0f, farmtech_extra); Dictionary residence = produce_item_positional("residence", item, residence_position, 0.0f, residence_extra); queue.push_back(field); queue.push_back(farmtech); queue.push_back(residence); } void TownQueue::_bind_methods() { ClassDB::bind_method(D_METHOD("produce_item", "item_name", "parent", "extra"), &TownQueue::produce_item, DEFVAL(Dictionary()), DEFVAL(Dictionary())); ClassDB::bind_method(D_METHOD("produce_item_positional", "item_name", "parent", "position", "rotation", "extra"), &TownQueue::produce_item_positional, DEFVAL(0.0f), DEFVAL(Dictionary())); ClassDB::bind_method(D_METHOD("startup", "town_extra"), &TownQueue::startup); ClassDB::bind_method(D_METHOD("push_back", "item"), &TownQueue::push_back); ClassDB::bind_method(D_METHOD("push_front", "item"), &TownQueue::push_front); ClassDB::bind_method(D_METHOD("pop_front"), &TownQueue::pop_front); ClassDB::bind_method(D_METHOD("push_back_delayed", "item"), &TownQueue::push_back_delayed); ClassDB::bind_method(D_METHOD("register_callback", "item_name", "obj", "func"), &TownQueue::register_callback); ClassDB::bind_method(D_METHOD("register_default_callback", "obj", "func"), &TownQueue::register_default_callback); ClassDB::bind_method(D_METHOD("unregister_callback", "item_name"), &TownQueue::unregister_callback); ClassDB::bind_method(D_METHOD("process"), &TownQueue::process); ClassDB::bind_method(D_METHOD("allocate_space", "aabb", "rotation", "patch"), &TownQueue::allocate_space); } bool TownQueue::allocate_space(const AABB& aabb, float rotation, const Dictionary &patch) { int i; Transform xform = Transform().rotated(Vector3(0, 1.0f, 0), rotation); const PoolVector &polygon = patch["polygon"]; Vector3 size = aabb.get_size(); Vector3 center = aabb.get_position() + Vector3(size.x, 0.1f, size.z) * 0.5f; PoolVector polygon2d = patch["polygon2d"]; Vector p2d; p2d.resize(polygon2d.size()); for (i = 0; i < polygon2d.size(); i++) p2d.write[i] = polygon2d.read()[i]; if (!Geometry::is_point_in_polygon(Vector2(center.x, center.z), p2d)) return false; for (i = 0; i < polygon.size(); i++) { Vector3 p1 = xform.xform_inv(polygon.read()[i]); Vector3 p2 = xform.xform_inv(polygon.read()[(i + 1) % polygon.size()]); p1.y = 0.1f; p2.y = 0.2f; if (aabb.intersects_segment(p1, p2)) return false; } return true; } Dictionary TownQueue::produce_item_positional(const StringName &item_name, const Dictionary &parent, const Vector3 &position, float rotation, const Dictionary &extra) { Dictionary item = produce_item(item_name, parent, extra); set_item_data(item, "position", position); set_item_data(item, "rotation", rotation); return item; } TownQueue::TownQueue() { relaxations = 3; }