diff --git a/src/modules/stream/road_lot.cpp b/src/modules/stream/road_lot.cpp new file mode 100644 index 0000000..5b907d8 --- /dev/null +++ b/src/modules/stream/road_lot.cpp @@ -0,0 +1,1308 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include "buildings_data.h" +#include "base_data.h" +#include "road_lot.h" +std::pair Lot::polygon::split() const +{ + int i, j; + float split_polygons_area = 0.0f; + float split_polygons_area_min = 0.0f; + std::pair split_polygons; + std::vector edges; + for (i = 0; i < (int)points.size(); i++) { + Vector3 p0 = points[i]; + Vector3 p1 = points[(i + 1) % points.size()]; + if (p0.distance_squared_to(p1) > 100 * 100) + edges.push_back(i); + } + if (edges.size() == 0) { + for (i = 0; i < (int)points.size(); i++) { + Vector3 p0 = points[i]; + Vector3 p1 = points[(i + 1) % points.size()]; + edges.push_back(i); + } + } + for (i = 0; i < (int)edges.size(); i++) { + Vector3 p0 = points[edges[i]]; + Vector3 p1 = points[(edges[i] + 1) % points.size()]; + Transform xf = + Transform(Basis(), p0).looking_at(p1, Vector3(0, 1, 0)); + AABB base; + base.position = xf.xform_inv(p0); + base.expand_to(xf.xform_inv(p1)); + for (j = 0; j < (int)points.size(); j++) + base.expand_to(xf.xform_inv(points[j])); + Vector3 mpos0, mpos1; + if (base.size.x > base.size.z) { + mpos0 = base.position + + Vector3(base.size.x, 0, 0) * 0.5f; + mpos1 = mpos0 + Vector3(0, 0, base.size.z); + mpos0 = xf.xform(mpos0); + mpos1 = xf.xform(mpos1); + } else { + mpos0 = base.position + + Vector3(0, 0, base.size.z) * 0.5f; + mpos1 = mpos0 + Vector3(base.size.x, 0, 0); + mpos0 = xf.xform(mpos0); + mpos1 = xf.xform(mpos1); + } + mpos0.y = 0; + mpos1.y = 0; + /* mpos0 is at start of cut */ + + Vector3 n = + (mpos1 - mpos0).cross(Vector3(0, 1, 0)).normalized(); + Plane pl1(mpos0 - n * 1.5f, n); + Plane pl2(mpos0 + n * 1.5f, -n); + Vector pdata; + pdata.resize(points.size()); + memcpy(pdata.ptrw(), points.data(), + points.size() * sizeof(Vector3)); + struct polygon poly1, poly2; + Vector rpdata = Geometry::clip_polygon(pdata, pl1); + poly1.points.resize(rpdata.size()); + memcpy(poly1.points.data(), rpdata.ptr(), + rpdata.size() * sizeof(Vector3)); + poly1.update_aabb(); + rpdata = Geometry::clip_polygon(pdata, pl2); + poly2.points.resize(rpdata.size()); + memcpy(poly2.points.data(), rpdata.ptr(), + rpdata.size() * sizeof(Vector3)); + poly2.update_aabb(); + float area1 = poly1.area(); + float area2 = poly2.area(); + if (area1 <= 4.0f || area2 <= 4.0f) + continue; + float area_min = MIN(area1, area2); + float area = area1 + area2; + if (split_polygons_area_min < area_min && + split_polygons_area < area) { + split_polygons = { poly1, poly2 }; + split_polygons_area_min = area_min; + split_polygons_area = area; + } + } + return split_polygons; +} + +int Lot::polygon::longest_edge() const +{ + return 0; +} + +void Lot::polygon::update_aabb() +{ + aabb = AABB(); + int i; + for (i = 0; i < (int)points.size(); i++) { + if (aabb.is_equal_approx(AABB())) + aabb.position = points[i]; + else + aabb.expand_to(points[i]); + } + assert(aabb.size.length() < 100000.0f * 100000.0f); + area_cache = Geometry::find_polygon_area(points.data(), points.size()); +} + +template static Vector get_polygon2(const T &polygon) +{ + int i; + Vector ret; + ret.resize(polygon.size()); + for (i = 0; i < (int)polygon.size(); i++) + ret.write[i] = Vector2(polygon[i].x, polygon[i].z); + return ret; +} + +bool Lot::polygon::intersects(const polygon *other) const +{ + print_line("intersection_test"); + dump(); + other->dump(); + Vector p1 = get_polygon2(points); + Vector p2 = get_polygon2(other->points); + Vector > intersection = + Geometry::intersect_polygons_2d(p1, p2); + if (intersection.size() > 0) { + intersections.push_back(intersection[0]); + assert(false); + } +#if 0 + int i, j; + print_line("intersection_test"); + dump(); + other->dump(); + for (i = 0; i < points.size(); i++) { + int i0 = i; + int i1 = (i + 1) % points.size(); + Vector3 normal = (points[i1] - points[i0]) + .normalized() + .cross(Vector3(0, 1, 0)); + for (j = 0; j < other->points.size(); j++) { + int j0 = j; + int j1 = (j + 1) % other->points.size(); + Vector3 m0 = other->points[j0]; + Vector3 m1 = other->points[j1]; + } + } +#endif + return false; +} + +void Lot::polygon::dump() const +{ + int i; + print_line("aabb: " + aabb.operator String()); + for (i = 0; i < (int)points.size(); i++) + print_line("polygon: " + itos(i) + " " + + points[i].operator String()); +} + +bool Lot::polygon::is_inside(const Vector3 &point) const +{ + Vector pt = get_polygon2(points); + return Geometry::is_point_in_polygon(Vector2(point.x, point.z), pt); +} + +bool Lot::polygon::is_inside(const Vector2 &point) const +{ + Vector pt = get_polygon2(points); + return Geometry::is_point_in_polygon(point, pt); +} + +/* check if any point of rect is inside polygon */ +static bool has_points(const Rect2 &rect, const Vector &pt) +{ + bool inside = false; + Vector2 x0 = rect.position; + Vector2 x1 = rect.position + Vector2(rect.size.x, 0); + Vector2 x2 = rect.position + rect.size; + Vector2 x3 = rect.position + Vector2(0, rect.size.y); + if (Geometry::is_point_in_polygon(rect.get_center(), pt)) + inside = true; + if (Geometry::is_point_in_polygon(x0, pt)) + inside = true; + if (Geometry::is_point_in_polygon(x1, pt)) + inside = true; + if (Geometry::is_point_in_polygon(x2, pt)) + inside = true; + if (Geometry::is_point_in_polygon(x3, pt)) + inside = true; + return inside; +} +// using this because hurts +static bool intersect_polys(const Rect2 &rect, const Vector &pt) +{ + int i; + Vector2 x0 = rect.position; + Vector2 x1 = rect.position + Vector2(rect.size.x, 0); + Vector2 x2 = rect.position + rect.size; + Vector2 x3 = rect.position + Vector2(0, rect.size.y); + Vector prect; + prect.resize(4); + prect.write[0] = x0; + prect.write[1] = x1; + prect.write[2] = x2; + prect.write[3] = x3; + Vector > result = + Geometry::intersect_polygons_2d(pt, prect); + assert(result.size() == 0 || result.size() == 2); + return result.size() != 0; +} + +bool Lot::polygon::is_inside(const Rect2 &rect) const +{ + Vector pt = get_polygon2(points); + Rect2 lot_rect; + int i; + for (i = 0; i < (int)pt.size(); i++) { + if (i == 0) + lot_rect.position = pt[i]; + else + lot_rect.expand_to(pt[i]); + } + if (!lot_rect.encloses(rect) && !lot_rect.intersects(rect)) + return false; + if (has_points(rect, pt)) + return true; + return intersect_polys(rect, pt); +} +bool Lot::polygon::is_inside(const AABB &aabb) const +{ + Vector pt = get_polygon2(points); + Vector normals; + normals.resize(points.size()); + Rect2 rect; + rect.position = Vector2(aabb.position.x, aabb.position.z); + rect.size = Vector2(aabb.size.x, aabb.size.z); + bool has_points = false; + bool has_intersections = false; + int i; + for (i = 0; i < (int)pt.size(); i++) + if (rect.has_point(pt[i])) { + has_points = true; + break; + } + if (has_points) + return true; + for (i = 0; i < (int)pt.size(); i++) { + int i0 = i; + int i1 = (i + 1) % pt.size(); + Vector2 p0 = pt[i0]; + Vector2 p1 = pt[i1]; + if (rect.intersects_segment(p0, p1)) { + has_intersections = true; + break; + } + } + if (has_intersections) + return true; + // no points and no intersections means either enclosed in polygon or not intersecting at all + // enclosed case can be checked by checking Rect2 center in polygon. + return is_inside(rect.get_center()); +} + +float Lot::polygon::area() const +{ + if (area_cache <= 0.0f) + return Geometry::find_polygon_area(points.data(), + points.size()); + return area_cache; +} + +#if 0 +struct CirclePacker { + struct grid_item; + struct pack_item { + int type; + Vector2 size; + float radius; + Vector2 position; + std::vector tiles; + }; + struct grid_item { + int x; + int y; + std::vector cell_items; + }; + Vector2 size; + int grid_divs; + Vector2i grid_size; + float pad; + std::vector items; + std::vector grid; + CirclePacker(float width, float height, int grid_divs, float pad) + : size(width, height) + , grid_divs(grid_divs) + , grid_size(Math::ceil(width / grid_divs), + Math::ceil(height / grid_divs)) + , pad(pad) + { + generate_grid(); + } + void generate_grid() + { + int i, j; + grid.resize(grid_divs * grid_divs); + for (i = 0; i < grid_divs; i++) + for (j = 0; j < grid_divs; j++) + grid[i * grid_divs + j] = { j, i, {} }; + } + float circle_distance(const struct pack_item &c1, + const struct pack_item &c2) + { + return c1.position.distance_to(c2.position) - + (c1.radius / 2 + c2.radius / 2); + } + struct grid_item &get_tile(float x, float y) + { + return grid[grid_divs * (int)Math::floor(x / grid_size.x) + + (int)Math::floor(x / grid_size.x)]; + } + std::vector + get_grid_tiles_around(const struct pack_item &c) + { + int tl[2] = { (int)Math::floor((c.position.x - c.radius - pad) / + grid_size.x), + (int)Math::floor((c.position.y - c.radius - pad) / + grid_size.y) }, + br[2] = { (int)Math::floor((c.position.x + c.radius - pad) / + grid_size.x), + (int)Math::floor((c.position.y + c.radius - pad) / + grid_size.y) }; + std::vector tiles; + int i, j; + for (i = tl[0]; i <= br[0]; i++) + for (j = tl[1]; j <= br[1]; i++) { + if (i < 0 || j < 0 || i >= grid_divs || + j >= grid_divs) + continue; + tiles.push_back(&grid[i * grid_divs + j]); + } + return tiles; + } + struct pack_item try_add_circle(float x, float y, + float min_radius = 5.0f, + float max_radius = 200.0f, + bool actually_add = true) + { + int i, j, k; + struct pack_item c1 = { 0, { 0, 0 }, min_radius, { x, y } }; + while (true) { + if (c1.position.x - c1.radius < 0 || + c1.position.x + c1.radius > size.x || + c1.position.y - c1.radius < 0 || + c1.position.y + c1.radius > size.y) + return { -1, { 0, 0 }, 0, { 0, 0 } }; + std::vector tiles = + get_grid_tiles_around(c1); + for (i = 0; i < (int)tiles.size(); i++) + for (j = 0; + j < (int)tiles[i]->cell_items.size(); + j++) { + float d = circle_distance( + c1, tiles[i]->cell_items[j]); + if (d - pad < 0) { + if (c1.radius == min_radius) + return { -1, + { 0, 0 }, + 0, + { 0, 0 } }; + else { + if (actually_add) { + for (k = 0; + k < + (int)tiles + .size(); + k++) { + c1.tiles.push_back( + tiles[k]); + tiles[k]->cell_items + .push_back( + c1); + } + items.push_back( + c1); + } + return c1; + } + } + } + c1.radius += 1; + if (c1.radius > max_radius) { + if (actually_add) { + for (k = 0; k < (int)tiles.size(); + k++) { + tiles[k]->cell_items.push_back( + c1); + /* record position too */ + } + items.push_back(c1); + } + return c1; + } + } + } + void add_circle(struct pack_item &c) + { + int i; + if (c.position.x - c.radius < 0 || + c.position.x + c.radius > size.x || + c.position.y - c.radius < 0 || + c.position.y + c.radius > size.y) { + std::vector grid = + get_grid_tiles_around(c); + for (i = 0; i < (int)grid.size(); i++) { + grid[i]->cell_items.push_back(c); + c.tiles.push_back(grid[i]); + } + items.push_back(c); + } + } + std::vector + try_to_add_shape(std::vector &circles, + bool actually_add = true) + { + int i; + for (i = 0; i < (int)circles.size(); i++) { + if (try_add_circle(circles[i].position.x, + circles[i].position.y, + circles[i].radius, circles[i].radius, + false) + .type < 0) + return std::vector(); + } + if (actually_add) + for (i = 0; i < (int)circles.size(); i++) + add_circle(circles[i]); + return circles; + } +}; +#endif + +struct LotPacker { + ConfigFile config; + static LotPacker *singleton; + struct pack_item { + Rect2 rect; + AABB aabb; + Transform rotation; + String building_type; + }; + struct grid_item { + Rect2 rect; + bool border; + std::vector items; + }; + struct LotGrid { + Rect2 rect; + std::vector grid_items; + std::vector outside; + std::vector pack_items; + }; + struct LotBuildings { + Vector > buildings; + Vector > polygons; + }; + struct Residental {}; + struct Commercial {}; + struct Industrial {}; + struct PreOccupied {}; + Vector2i size; + Rect2 rect; + Vector2 cell_size; + struct pack_item; + struct grid_node { + flecs::entity e; + Rect2 rect; + std::vector items; + }; + std::vector grid_layout; + struct config_buildings { + Vector buildings; + float area; + }; + Vector lot_buildings_residental, + lot_buildings_commercial, lot_buildings_industrial; + void get_building_list(const String &varname, + Vector &lbs) + { + lbs.clear(); + Array lb = config.get_value("road", varname, Array()); + int i, j; + for (i = 0; i < (int)lb.size(); i++) { + struct config_buildings cb; + cb.area = 0.0f; + Array entries = lb[i]; + for (j = 0; j < entries.size(); j++) { + String bname = entries[j]; + cb.buildings.push_back(bname); + AABB aabb = BuildingsData::get_singleton() + ->building_aabbs[bname]; + cb.area += aabb.size.x * aabb.size.z; + print_line("config building: " + bname); + } + print_line("config building: area: " + + String::num(cb.area)); + lbs.push_back(cb); + } + struct lbsort { + _FORCE_INLINE_ bool + operator()(const struct config_buildings &b1, + const struct config_buildings &b2) const + { + return (b1.area < b2.area); + } + }; + lbs.sort_custom(); + lbs.invert(); + } + LotPacker(Vector2 cell_size) + : cell_size(cell_size) + { + BaseData::get_singleton()->get().component(); + BaseData::get_singleton()->get().component(); + BaseData::get_singleton()->get().component(); + BaseData::get_singleton()->get().component(); + BaseData::get_singleton()->get().component(); + BaseData::get_singleton()->get().component(); + Error err = config.load("res://config/stream.conf"); + assert(err == OK); + get_building_list("lot_buildings_residental", + lot_buildings_residental); + get_building_list("lot_buildings_commercial", + lot_buildings_commercial); + get_building_list("lot_buildings_industrial", + lot_buildings_industrial); + } + static LotPacker *get_singleton() + { + if (!singleton) + singleton = memnew(LotPacker(Vector2(16, 16))); + return singleton; + } + + static inline Rect2 aabb2rect(const AABB &aabb) + { + Vector2 position(aabb.position.x, aabb.position.z); + Vector2 size(aabb.size.x, aabb.size.z); + Rect2 ret(position, size); + return ret; + } + static inline Vector2 v3tov2(const Vector3 &v) + { + return Vector2(v.x, v.z); + } + /* optimization needed */ + bool is_point_in_polygon(flecs::entity e, const Vector2 &point) + { + assert(e.has()); + const Lot *lot = e.get(); + Vector polygon; + polygon.resize(lot->polygon.points.size()); + int i; + for (i = 0; i < polygon.size(); i++) + polygon.write[i] = v3tov2(lot->polygon.points[i]); + return Geometry::is_point_in_polygon(point, polygon); + } + bool is_border_cell(flecs::entity e, const Rect2 &rect) + { + int i; + assert(e.has()); + const Lot *lot = e.get(); + bool ret = false; + for (i = 0; i < (int)lot->polygon.points.size(); i++) { + int i0 = i; + int i1 = (i + 1) % lot->polygon.points.size(); + Vector2 p0 = v3tov2(lot->polygon.points[i0]); + Vector2 p1 = v3tov2(lot->polygon.points[i1]); + if (rect.intersects_segment(p0, p1)) { + ret = true; + break; + } + } + return ret; + } + void add_lot(flecs::entity e) + { + int i; + assert(e.has()); + const Lot *lot = e.get(); + Rect2 rect = aabb2rect(lot->polygon.aabb); + rect = rect.grow(MAX(cell_size.x, cell_size.y) * 2); + int grid_width = (int)Math::ceil(rect.size.x / cell_size.x); + int grid_height = (int)Math::ceil(rect.size.y / cell_size.y); + std::vector grid_items; + std::vector pack_items; + grid_items.reserve(grid_width * grid_height); + print_line("add_lot: " + String(e.path())); + print_line("add_lot: rect: " + rect.operator String()); + std::vector outside; + for (i = 0; i < grid_width * grid_height; i++) { + int tx = i % grid_width; + int ty = i / grid_width; + Vector2 position = + rect.position + + Vector2(cell_size.x * tx, cell_size.y * ty); + Vector2 center_position = position + cell_size * 0.5f; + if (is_point_in_polygon(e, center_position)) { + Rect2 cell_rect(position, cell_size); + print_line("add_lot: cell: rect: " + + cell_rect.operator String()); + bool border = is_border_cell(e, cell_rect); + if (border) + print_line("border"); + grid_items.push_back({ cell_rect, border }); + } else { + Rect2 cell_rect(position, cell_size); + outside.push_back(cell_rect); + } + } + grid_items.shrink_to_fit(); + if (e.has()) { + LotGrid *lotgrid = e.get_mut(); + lotgrid->grid_items.clear(); + lotgrid->outside.clear(); + lotgrid->pack_items.clear(); + } + e.set({ rect, grid_items, outside, pack_items }); + } + inline bool check_basic_fit(const Lot *lot, const AABB &item_aabb) + { + const AABB &aabb = lot->polygon.aabb; + if (aabb.size.x - cell_size.x / 2.0f < item_aabb.size.x) { + return false; + } + if (aabb.size.z - cell_size.y / 2.0f < item_aabb.size.z) { + return false; + } + return true; + } + inline bool outside(const LotGrid *lotgrid, const Rect2 &test_rect) + { + int j; + bool good = true; + assert(lotgrid->outside.size() > 0); + for (j = 0; j < (int)lotgrid->outside.size(); j++) + if (test_rect.encloses(lotgrid->outside[j])) { + good = false; + break; + } + if (!good) + return !good; + for (j = 0; j < (int)lotgrid->outside.size(); j++) + if (test_rect.intersects(lotgrid->outside[j])) { + good = false; + break; + } + return !good; + } + inline bool busy(const LotGrid *lotgrid, const Rect2 &test_rect) + { + int j; + bool good = true; + assert(lotgrid->grid_items.size() > 0); + int cell_count = 0; + for (j = 0; j < (int)lotgrid->grid_items.size(); j++) + if (test_rect.intersects(lotgrid->grid_items[j].rect) || + test_rect.encloses(lotgrid->grid_items[j].rect)) { + cell_count++; + if (lotgrid->grid_items[j].items.size() > 0) { + good = false; + break; + } + if (lotgrid->grid_items[j].border) { + good = false; + break; + } + } + assert(!good || cell_count > 0); + return !good; + } + AABB pack_item(flecs::entity e, const AABB &item_aabb_, + const String &building_type, int rotation) + { + int i, j; + assert(e.has()); + assert(e.has()); + const Lot *lot = e.get(); + const AABB &aabb = lot->polygon.aabb; + LotGrid *lotgrid = e.get_mut(); + assert(lot); + assert(lotgrid); + Transform rot(Basis().rotated(Vector3(0, 1, 0), + Math_PI * (float)rotation / 2), + Vector3()); + AABB item_aabb_rot = rot.xform(item_aabb_); + + AABB result_aabb = item_aabb_rot; + bool good = true; + bool loop_ok = false; + Rect2 lot_rect = aabb2rect(aabb); + if (!check_basic_fit(lot, item_aabb_rot)) { + print_line("item won't fit 2"); + goto out_bad; + } + if (lotgrid->outside.size() == 0 || + lotgrid->grid_items.size() == 0) { + print_line("bad lot"); + goto out_bad; + } + + for (i = 0; i < (int)lotgrid->grid_items.size(); i++) { + if (i == (int)lotgrid->grid_items.size() - 1) + print_line("last iteration"); + if (i == 0) + print_line("first iteration"); + good = true; + result_aabb = item_aabb_rot; + if (lotgrid->grid_items[i].items.size() > 0) { + print_line("cell already occupied"); + continue; + } + Rect2 test_rect = aabb2rect(item_aabb_rot); + test_rect.position = + lotgrid->grid_items[i].rect.position; + print_line("LOT: " + lot_rect.operator String()); + print_line("ITEM: " + test_rect.operator String()); + if (!aabb2rect(aabb).encloses(test_rect)) { + print_line("item outside lot aabb"); + continue; + } + Vector2 testp = test_rect.get_center(); + if (!lot->polygon.is_inside(testp)) { + print_line("item is outside polygon"); + continue; + } + if (!good) + continue; + good = !outside(lotgrid, test_rect); + if (!good) { + print_line("item is outside"); + continue; + } + good = !busy(lotgrid, test_rect); + if (!good) { + print_line("item is busy"); + continue; + } + result_aabb.position.x = test_rect.position.x; + result_aabb.position.z = test_rect.position.y; + // if (result_aabb.get_center()) {} + assert(lot->polygon.is_inside( + result_aabb.get_center())); + assert(result_aabb.get_center().distance_to(Vector3()) > + 2); + print_line("INSIDE PASSED"); + loop_ok = true; + break; + } + if (!loop_ok) + goto out_bad; + if (good) { + assert(loop_ok); + if (!lot->polygon.is_inside(result_aabb.get_center())) { + print_line("item outside polygon"); + goto out_bad; + } + struct pack_item item; + item.rotation = rot; + + item.aabb = result_aabb; + item.rect = aabb2rect(result_aabb); + assert(lot->polygon.is_inside( + result_aabb.get_center())); + if (item.aabb.get_center().distance_to(Vector3()) <= + 2) { + print_line("pack problem: "); + lot->polygon.dump(); + print_line("AABB: " + + lot->polygon.aabb.operator String()); + } + assert(item.rect.get_center().distance_to(Vector2()) > + 2); + item.building_type = building_type; + lotgrid->pack_items.push_back(item); + struct LotPacker::pack_item *item_ptr = + &lotgrid->pack_items.back(); + for (i = 0; i < (int)lotgrid->grid_items.size(); i++) { + Rect2 check_rect = lotgrid->grid_items[i].rect; + if (item.rect.encloses(check_rect) || + item.rect.intersects(check_rect)) { + lotgrid->grid_items[i].items.push_back( + item_ptr); + } + } + e.get_mut()->polygon.update_aabb(); + e.modified(); + e.modified(); + } else { + print_line("can't fit or no free cell found"); + goto out_bad; + } + assert(!result_aabb.is_equal_approx(AABB())); + return result_aabb; +out_bad: + return AABB(); + } + + bool allowed_building(const String &key) const + { + Array ignore_prefixes = config.get_value( + "road", "allow_building_prefixes", Array()); + int i; + bool ret = false; + for (i = 0; i < ignore_prefixes.size(); i++) + if (key.begins_with(ignore_prefixes[i])) { + ret = true; + break; + } + return ret; + } + bool tmp_found = false; + int office_count = 0; + AABB get_building_aabb(const String &key) + { + const struct BuildingsData::building &b = + BuildingsData::get_singleton()->get_building(key); + const AABB &building_aabb = + BuildingsData::get_singleton()->building_aabbs[b.id]; + return building_aabb; + } + Transform get_building_transform(const String &key) + { + const struct BuildingsData::building &b = + BuildingsData::get_singleton()->get_building(key); + return b.xform; + } + AABB get_aabb_transformed(const String &key) + { + AABB building_aabb = get_building_aabb(key); + Transform xform = get_building_transform(key); + AABB xaabb = xform.xform(building_aabb); + return xaabb; + } + Rect2 get_rect_transformed(const String &key) + { + return aabb2rect(get_aabb_transformed(key)); + } + void create() + { + int lot_count = 0; + BaseData::get_singleton() + ->get() + .query_builder() + .write() + .write() + .build() + .each([&](flecs::entity e, Lot &lot_) { + assert(e.get()->polygon.points.size() > 0); + List buildings; + BuildingsData::get_singleton() + ->get_building_keys_list(&buildings); + List::Element *be = buildings.front(); + e.get_mut()->polygon.update_aabb(); + if (e.has()) + return; + while (be) { + const String &key = be->get(); + const struct BuildingsData::building &b = + BuildingsData::get_singleton() + ->get_building(key); + if (key.begins_with("road__")) { + be = be->next(); + continue; + } + if (allowed_building(b.id)) { + be = be->next(); + continue; + } + AABB lot_aabb = + e.get()->polygon.aabb; + Rect2 lot_rect = aabb2rect( + e.get()->polygon.aabb); + Rect2 building_rect = + get_rect_transformed(key); + Transform xform = + get_building_transform(key); + Vector2 building_pos = Vector2( + xform.origin.x, xform.origin.z); + bool intersects = false; + if (e.get()->polygon.is_inside( + building_rect)) + intersects = true; + assert(e.get() + ->polygon.points.size() > + 0); + if (intersects) { + assert(e.get() + ->polygon.points + .size() > 0); + e.add(); + assert(e.get() + ->polygon.points + .size() > 0); + LotBuildings &lb = + e.ensure(); + lb.buildings.push_back( + { building_rect, key }); + Vector pt = + get_polygon2( + e.get() + ->polygon + .points); + assert(pt.size() > 0); + lb.polygons.push_back(pt); + e.modified(); + } + be = be->next(); + } + }); + BaseData::get_singleton() + ->get() + .query_builder() + .with() + .build() + .each([&](flecs::entity e, Lot &lot, LotBuildings &lb) { + int i; + for (i = 0; i < lb.buildings.size(); i++) { + const Pair &mp = + lb.buildings[i]; + const String &key = mp.second; + const struct BuildingsData::building &b = + BuildingsData::get_singleton() + ->get_building(key); + Rect2 building_rect = + get_rect_transformed(key); + Rect2 lot_rect = + aabb2rect(lot.polygon.aabb); + if (b.id == "office-exterior") { + print_line("id: " + b.id); + print_line("xform: " + + b.xform.origin. + operator String()); + print_line("rect: " + + building_rect. + operator String()); + print_line("lot rect: " + + lot_rect. + operator String()); + tmp_found = true; + office_count++; + } + } + }); + BaseData::get_singleton() + ->get() + .query_builder() + .without() + .build() + .each([&](flecs::entity e, Lot &lot) { +#if 0 + int i; + List buildings; + BuildingsData::get_singleton() + ->get_building_keys_list(&buildings); + List::Element *be = buildings.front(); + while (be) { + const String &key = be->get(); + if (key.begins_with("road__")) { + be = be->next(); + continue; + } + const struct BuildingsData::building &b = + BuildingsData::get_singleton() + ->get_building(key); + const AABB &building_aabb = + BuildingsData::get_singleton() + ->building_aabbs[b.id]; + Vector2 building_pos = + Vector2(b.xform.origin.x, + b.xform.origin.z); + AABB building_aabb_transformed = + b.xform.xform(building_aabb); + Rect2 building_rect = aabb2rect( + building_aabb_transformed); + AABB lot_aabb = lot.polygon.aabb; + Rect2 lot_rect = + aabb2rect(lot.polygon.aabb); + bool intersects = false; + + if (allowed_building(b.id)) { + be = be->next(); + continue; + } +#if 0 + if (intersects && + b.id == "office-exterior") { + assert(lot.polygon.is_inside( + building_rect)); + } +#endif + // intersects = false; + if (!lot.polygon.is_inside( + building_rect)) + intersects = false; + if (!intersects) { + be = be->next(); + continue; + } + if (intersects && + b.id == "office-exterior") { + print_line("id: " + b.id); + print_line("xform: " + + b.xform.origin. + operator String()); + print_line("rect: " + + building_rect. + operator String()); + print_line("lot rect: " + + lot_rect. + operator String()); + tmp_found = true; + office_count++; + } + if (intersects) { + Vector3 position = + b.xform.origin; + e.add(); + print_line("occupied: " + key); + print_line("occupied: " + b.id); + lot.polygon.dump(); + print_line(position. + operator String()); + } +#if 0 + lot_aabb.position.y = + MIN(-1.0f, + building_aabb.position.y); + lot_aabb.position.y -= 1.0f; + lot_aabb.size.y += + building_aabb.size.y + 1.0f; + if (!lot_aabb.intersects(b.xform.xform( + building_aabb)) && + !lot_aabb.encloses(b.xform.xform( + building_aabb))) { + be = be->next(); + continue; + } +#endif + be = be->next(); + } +#endif + if (e.has()) + return; + if ((lot_count % 5) == 0) + e.add(); + else if (lot_count % 3 == 0) + e.add(); + else + e.add(); + lot_count++; + }); + assert(tmp_found); + print_line("office_count: " + itos(office_count)); + // assert(office_count > 1); + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity e, Lot &lot) { add_lot(e); }); + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity e, Lot &lot) { + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity we, Lot &wlot) { + if (e != we) + lot.polygon.intersects( + &wlot.polygon); + }); + }); + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity e, Lot &lot) { + lot.polygon.update_aabb(); + }); + } + float get_required_area(const Vector &buildings) const + { + float ret = 0.0f; + int i; + for (i = 0; i < (int)buildings.size(); i++) { + const String &building = buildings[i]; + const AABB &building_aabb = + BuildingsData::get_singleton() + ->building_aabbs[building]; + ret += building_aabb.size.x * building_aabb.size.z; + } + return ret; + } + int get_selected_building(const Lot &lot, + Vector &lbs) + { + int i; + float max_area = lot.polygon.area(); + int selected = -1; + for (i = 0; i < (int)lbs.size(); i++) { + if (max_area > lbs[i].area) { + selected = i; + break; + } + } + return selected; + } + int get_selected_building_max(const Lot &lot, + Vector &lbs) + { + int i; + float max_area = lot.polygon.area(); + int selected = get_selected_building(lot, lbs); + if (selected < 0) + return selected; + int select_max = selected; + for (i = selected; i < (int)lbs.size(); i++) { + if (max_area > lbs[i].area) { + select_max = i; + if (select_max - selected > 10) + break; + } + } + return select_max; + } + int get_idx(const Lot &lot) const + { + uint32_t idx1 = (uint32_t)(lot.polygon.points[0].x) % 0x0f; + uint32_t idx2 = (uint32_t)(lot.polygon.points[0].y) % 0x0f; + uint32_t idx3 = (uint32_t)(lot.polygon.points[0].z) % 0x0f; + uint32_t idx = (idx1 << 8) | (idx2 << 4) | idx3; + return idx; + } + void pack_lots() + { + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity e, Lot &lot) { + lot.polygon.update_aabb(); + }); + int residental_count = 0; + BaseData::get_singleton() + ->get() + .query_builder() + .build() + .each([&](flecs::entity e, const Lot &lot, + LotPacker::LotGrid &lotgrid) { + int i; + print_line("pack_lot: " + String(e.path())); + if (e.has()) + return; + int idx = get_idx(lot); + Vector lbs; + if (e.has()) + lbs = lot_buildings_residental; + else if (e.has()) + lbs = lot_buildings_industrial; + else if (e.has()) + lbs = lot_buildings_commercial; + else + lbs = lot_buildings_residental; + int selected = get_selected_building(lot, lbs); + int select_max = + get_selected_building_max(lot, lbs); + + if (selected >= 0) { + // TODO: add variety; + if (select_max > selected) + selected = selected + + (idx % (select_max - + selected)); + while (1) { + Vector buildings = + lbs[selected].buildings; + bool ok = true; + int rotation = (idx >> 2) % 4; + for (i = 0; + i < (int)buildings.size(); + i++) { + const String &building = + buildings[i]; + const AABB &building_aabb = + BuildingsData::get_singleton() + ->building_aabbs + [building]; + AABB result = pack_item( + e, + building_aabb, + building, + rotation); + if (result == AABB()) { + print_line( + "pack failed"); + ok = false; + break; + } +#if 0 + assert(!result.is_equal_approx( + AABB())); + Vector3 m = result.get_center(); + m.y = 0; + assert(!m.is_equal_approx( + Vector3())); + print_line("result: " + + result. + operator String()); +#endif + } + if (ok) + break; + else { + add_lot(e); + selected++; + if (selected >= + lbs.size()) + break; + continue; + } + } + } + residental_count++; + }); + } + void get_lot_data(flecs::entity e, List > *lot_data) + { + const LotGrid *lotgrid = e.get(); + if (!lotgrid) + return; + int i; + for (i = 0; i < (int)lotgrid->pack_items.size(); i++) + lot_data->push_back( + { lotgrid->pack_items[i].aabb, + lotgrid->pack_items[i].building_type }); + } + void get_lot_rotations(flecs::entity e, List *lot_xform) + { + const LotGrid *lotgrid = e.get(); + if (!lotgrid) + return; + int i; + for (i = 0; i < (int)lotgrid->pack_items.size(); i++) + lot_xform->push_back(lotgrid->pack_items[i].rotation); + } + void get_lot_buildings(flecs::entity e, + Vector > *lot_buildings) + { + const LotBuildings *lb = e.get(); + if (!lb) + return; + lot_buildings->clear(); + lot_buildings->append_array(lb->buildings); + } + Vector > get_lot_polygons(flecs::entity e) + { + const LotBuildings *lb = e.get(); + if (!lb) + return Vector >(); + return lb->polygons; + } +}; +LotPacker *LotPacker::singleton = nullptr; + +void Lot::pack() +{ + LotPacker::get_singleton()->create(); + LotPacker::get_singleton()->pack_lots(); +} + +void Lot::get_lot_data(flecs::entity e, List > *lot_data) +{ + LotPacker::get_singleton()->get_lot_data(e, lot_data); +} + +void Lot::get_lot_rotations(flecs::entity e, List *lot_xform) +{ + LotPacker::get_singleton()->get_lot_rotations(e, lot_xform); +} + +void Lot::get_lot_buildings(flecs::entity e, + Vector > *lot_buildings) +{ + LotPacker::get_singleton()->get_lot_buildings(e, lot_buildings); +} + +Vector > Lot::get_lot_polygons(flecs::entity e) +{ + return LotPacker::get_singleton()->get_lot_polygons(e); +} diff --git a/src/modules/stream/road_lot.h b/src/modules/stream/road_lot.h new file mode 100644 index 0000000..894a5de --- /dev/null +++ b/src/modules/stream/road_lot.h @@ -0,0 +1,38 @@ +/* ~/godot-projects/streaming_world/src/modules/stream/road_lot.h */ +#ifndef ROAD_LOT_H_ +#define ROAD_LOT_H_ +#include +#include +#include +#include "base_data.h" +#include "road_lines_data.h" +struct Lot { + struct polygon { + std::vector points; + AABB aabb; + std::pair split() const; + float area_cache; + float area() const; + int longest_edge() const; + void update_aabb(); + mutable Vector > intersections; + bool intersects(const struct polygon *other) const; + void dump() const; + bool is_inside(const Vector3 &point) const; + bool is_inside(const Vector2 &point) const; + bool is_inside(const AABB &aabb) const; + bool is_inside(const Rect2 &rect) const; + }; + struct polygon polygon; + static void pack(); + static void get_lot_data(flecs::entity e, + List > *lot_data); + static void get_lot_rotations(flecs::entity e, + List *lot_xform); + static void + get_lot_buildings(flecs::entity e, + Vector > *lot_buildings); + static Vector > get_lot_polygons(flecs::entity e); +}; + +#endif // ROAD_LOT_H_ \ No newline at end of file diff --git a/src/modules/stream/rtree/growth_regions.cpp b/src/modules/stream/rtree/growth_regions.cpp new file mode 100644 index 0000000..5f82439 --- /dev/null +++ b/src/modules/stream/rtree/growth_regions.cpp @@ -0,0 +1 @@ +#include "growth_regions.h" diff --git a/src/modules/stream/rtree/growth_regions.h b/src/modules/stream/rtree/growth_regions.h new file mode 100644 index 0000000..513fcc4 --- /dev/null +++ b/src/modules/stream/rtree/growth_regions.h @@ -0,0 +1,50 @@ +#ifndef GROWTH_REGIONS_H +#define GROWTH_REGIONS_H +#include +#include +#include +#include +#include "base_data.h" +#include "region_rect2.h" +struct region { + flecs::entity_t parent; + flecs::entity_t seed_et; + flecs::entity_t region_et; + RegionRect2i rect; + int remains_area; + bool can_grow_square; + bool can_move; + bool can_grow; + bool complete; + bool can_grow_region() const + { + bool ret = can_grow; + if (remains_area <= 0) + ret = false; + return ret; + } + bool update_region_size(RegionRect2i &mrect) + { + bool ret = false; + int old_area = rect.get_area(); + int new_area = mrect.get_area(); + int area_diff = new_area - old_area; + if (area_diff > 0) { + rect = mrect; + remains_area -= area_diff; + ret = true; + } + if (remains_area <= 0) { + can_grow_square = false; + can_grow = false; + } + flecs::log::dbg("update_region_size %d -> %d", area_diff, ret); + return ret; + } +}; +struct growth_regions { + List job_list; + bool complete; +}; + +#endif