mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
Automatically generate houses along empty residential roads, for https://github.com/cyipt/actdev/issues/53. Still need to prune out houses that hit existing map features, and save the output.
This commit is contained in:
parent
3449d49b6d
commit
29fb271afc
113
game/src/debug/building_procgen.rs
Normal file
113
game/src/debug/building_procgen.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
use geom::{Distance, Polygon};
|
||||||
|
use map_model::osm;
|
||||||
|
use widgetry::{
|
||||||
|
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Panel, SimpleState,
|
||||||
|
State, StyledButtons, TextExt, VerticalAlignment, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::{App, Transition};
|
||||||
|
|
||||||
|
pub struct BuildingProceduralGenerator {
|
||||||
|
houses: Drawable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildingProceduralGenerator {
|
||||||
|
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
|
||||||
|
let mut batch = GeomBatch::new();
|
||||||
|
let mut rng = app.primary.current_flags.sim_flags.make_rng();
|
||||||
|
for b in generate_buildings_on_empty_residential_roads(app, &mut rng) {
|
||||||
|
batch.push(Color::RED, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let panel = Panel::new(Widget::row(vec![
|
||||||
|
Line("Procedurally generated buildings")
|
||||||
|
.small_heading()
|
||||||
|
.draw(ctx),
|
||||||
|
ctx.style().btn_close_widget(ctx),
|
||||||
|
]))
|
||||||
|
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||||
|
.build(ctx);
|
||||||
|
SimpleState::new(
|
||||||
|
panel,
|
||||||
|
Box::new(BuildingProceduralGenerator {
|
||||||
|
houses: ctx.upload(batch),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleState<App> for BuildingProceduralGenerator {
|
||||||
|
fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &Panel) -> Transition {
|
||||||
|
match x {
|
||||||
|
"close" => Transition::Pop,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||||
|
ctx.canvas_movement();
|
||||||
|
Transition::Keep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||||
|
g.redraw(&self.houses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_buildings_on_empty_residential_roads(app: &App, rng: &mut XorShiftRng) -> Vec<Polygon> {
|
||||||
|
let map = &app.primary.map;
|
||||||
|
|
||||||
|
let mut lanes_with_buildings = HashSet::new();
|
||||||
|
for b in map.all_buildings() {
|
||||||
|
lanes_with_buildings.insert(b.sidewalk());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all sidewalks belonging to residential roads that have no buildings
|
||||||
|
let mut empty_sidewalks = Vec::new();
|
||||||
|
for l in map.all_lanes() {
|
||||||
|
if l.is_sidewalk()
|
||||||
|
&& !lanes_with_buildings.contains(&l.id)
|
||||||
|
&& map.get_r(l.parent).osm_tags.is(osm::HIGHWAY, "residential")
|
||||||
|
{
|
||||||
|
empty_sidewalks.push(l.id);
|
||||||
|
//houses.push(l.lane_center_pts.make_polygons(l.width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk along each sidewalk, trying to place some simple houses with a bit of setback from the
|
||||||
|
// road.
|
||||||
|
let mut houses = Vec::new();
|
||||||
|
for l in empty_sidewalks {
|
||||||
|
let lane = map.get_l(l);
|
||||||
|
let mut dist_along = rand_dist(rng, 1.0, 5.0);
|
||||||
|
while dist_along < lane.lane_center_pts.length() {
|
||||||
|
let (sidewalk_pt, angle) = lane.lane_center_pts.must_dist_along(dist_along);
|
||||||
|
let setback = rand_dist(rng, 13.0, 17.0);
|
||||||
|
let center = sidewalk_pt.project_away(setback, angle.rotate_degs(-90.0));
|
||||||
|
|
||||||
|
let width = rng.gen_range(4.0..7.0);
|
||||||
|
let height = rng.gen_range(4.0..7.0);
|
||||||
|
houses.push(
|
||||||
|
Polygon::rectangle(width, height)
|
||||||
|
.rotate(angle)
|
||||||
|
.translate(center.x() - width / 2.0, center.y() - height / 2.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
dist_along += Distance::meters(width.max(height)) + rand_dist(rng, 2.0, 4.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Remove buildings that hit other ones or parks/water or roads
|
||||||
|
|
||||||
|
houses
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_dist(rng: &mut XorShiftRng, low: f64, high: f64) -> Distance {
|
||||||
|
assert!(high > low);
|
||||||
|
Distance::meters(rng.gen_range(low..high))
|
||||||
|
}
|
@ -23,6 +23,7 @@ use crate::info::ContextualActions;
|
|||||||
use crate::sandbox::GameplayMode;
|
use crate::sandbox::GameplayMode;
|
||||||
|
|
||||||
mod blocked_by;
|
mod blocked_by;
|
||||||
|
mod building_procgen;
|
||||||
mod floodfill;
|
mod floodfill;
|
||||||
mod objects;
|
mod objects;
|
||||||
pub mod path_counter;
|
pub mod path_counter;
|
||||||
@ -113,6 +114,10 @@ impl DebugMode {
|
|||||||
.btn_outline_light_text("render to GeoJSON")
|
.btn_outline_light_text("render to GeoJSON")
|
||||||
.hotkey(Key::G)
|
.hotkey(Key::G)
|
||||||
.build_def(ctx),
|
.build_def(ctx),
|
||||||
|
ctx.style()
|
||||||
|
.btn_outline_light_text("procedurally generate buildings")
|
||||||
|
.hotkey(Key::P)
|
||||||
|
.build_def(ctx),
|
||||||
]),
|
]),
|
||||||
Text::from_all(vec![
|
Text::from_all(vec![
|
||||||
Line("Hold "),
|
Line("Hold "),
|
||||||
@ -316,6 +321,11 @@ impl State<App> for DebugMode {
|
|||||||
timer.stop("render");
|
timer.stop("render");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
"procedurally generate buildings" => {
|
||||||
|
return Transition::Replace(
|
||||||
|
building_procgen::BuildingProceduralGenerator::new(ctx, app),
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
Outcome::Changed => {
|
Outcome::Changed => {
|
||||||
|
Loading…
Reference in New Issue
Block a user