From 7296843923cde68468677b50130b82a40624fbf6 Mon Sep 17 00:00:00 2001 From: IsseW Date: Mon, 26 Sep 2022 16:46:30 +0200 Subject: [PATCH 01/14] simple bridges --- Cargo.lock | 1 + .../voxygen/element/ui/map/buttons/bridge.png | 3 + .../element/ui/map/buttons/bridge_bg.png | 3 + .../element/ui/map/buttons/bridge_hover.png | 3 + assets/voxygen/i18n/en/hud/map.ftl | 1 + common/net/src/msg/world_msg.rs | 1 + voxygen/src/hud/img_ids.rs | 3 + voxygen/src/hud/map.rs | 51 ++- voxygen/src/hud/minimap.rs | 3 + voxygen/src/session/settings_change.rs | 4 + voxygen/src/settings/interface.rs | 2 + world/Cargo.toml | 2 +- world/src/civ/mod.rs | 145 ++++++-- world/src/column/mod.rs | 2 +- world/src/lib.rs | 1 + world/src/site/economy/context.rs | 1 + world/src/site/mod.rs | 14 + world/src/site2/gen.rs | 116 ++++++ world/src/site2/mod.rs | 63 ++++ world/src/site2/plot.rs | 4 +- world/src/site2/plot/bridge.rs | 335 ++++++++++++++++++ world/src/site2/util/mod.rs | 122 ++++++- 22 files changed, 849 insertions(+), 31 deletions(-) create mode 100644 assets/voxygen/element/ui/map/buttons/bridge.png create mode 100644 assets/voxygen/element/ui/map/buttons/bridge_bg.png create mode 100644 assets/voxygen/element/ui/map/buttons/bridge_hover.png create mode 100644 world/src/site2/plot/bridge.rs diff --git a/Cargo.lock b/Cargo.lock index 350fbba9e3..f6cf1a6425 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7173,6 +7173,7 @@ dependencies = [ "fxhash", "hashbrown 0.12.3", "image", + "inline_tweak", "itertools", "kiddo 0.2.4", "lazy_static", diff --git a/assets/voxygen/element/ui/map/buttons/bridge.png b/assets/voxygen/element/ui/map/buttons/bridge.png new file mode 100644 index 0000000000..c388f91d68 --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/bridge.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00e71d600630bd5d957bcf0667983354077eebc5eacb2a07dc20f8eb83c9d815 +size 161 diff --git a/assets/voxygen/element/ui/map/buttons/bridge_bg.png b/assets/voxygen/element/ui/map/buttons/bridge_bg.png new file mode 100644 index 0000000000..1cbff1c96e --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/bridge_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11b18b58167244297621496c40015686f3a341063584d8ffc56b2fe1e9173715 +size 133 diff --git a/assets/voxygen/element/ui/map/buttons/bridge_hover.png b/assets/voxygen/element/ui/map/buttons/bridge_hover.png new file mode 100644 index 0000000000..39b5a05d0b --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/bridge_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd02532bfba385b1f9e5ce17e2fb3c93697e41be1bdc86e4867a76dcc6c6f67e +size 221 diff --git a/assets/voxygen/i18n/en/hud/map.ftl b/assets/voxygen/i18n/en/hud/map.ftl index 8f7635164e..a19834bf2d 100644 --- a/assets/voxygen/i18n/en/hud/map.ftl +++ b/assets/voxygen/i18n/en/hud/map.ftl @@ -14,6 +14,7 @@ hud-map-trees = Giant Trees hud-map-tree = Giant Tree hud-map-town = Town hud-map-castle = Castle +hud-map-bridge = Bridge hud-map-dungeon = Dungeon hud-map-difficulty_dungeon = Dungeon diff --git a/common/net/src/msg/world_msg.rs b/common/net/src/msg/world_msg.rs index 16986f1d0b..0a2ce94e6b 100644 --- a/common/net/src/msg/world_msg.rs +++ b/common/net/src/msg/world_msg.rs @@ -148,6 +148,7 @@ pub enum SiteKind { Tree, Gnarling, ChapelSite, + Bridge, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 9979dd203d..e633029646 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -458,6 +458,9 @@ image_ids! { mmap_site_castle: "voxygen.element.ui.map.buttons.castle", mmap_site_castle_hover: "voxygen.element.ui.map.buttons.castle_hover", mmap_site_castle_bg: "voxygen.element.ui.map.buttons.castle_bg", + mmap_site_bridge: "voxygen.element.ui.map.buttons.bridge", + mmap_site_bridge_hover: "voxygen.element.ui.map.buttons.bridge_hover", + mmap_site_bridge_bg: "voxygen.element.ui.map.buttons.bridge_bg", mmap_site_cave_bg: "voxygen.element.ui.map.buttons.cave_bg", mmap_site_cave_hover: "voxygen.element.ui.map.buttons.cave_hover", mmap_site_cave: "voxygen.element.ui.map.buttons.cave", diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 5f9d57792d..e9a33d01a6 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -63,6 +63,9 @@ widget_ids! { show_castles_img, show_castles_box, show_castles_text, + show_bridges_img, + show_bridges_box, + show_bridges_text, show_dungeons_img, show_dungeons_box, show_dungeons_text, @@ -215,6 +218,7 @@ impl<'a> Widget for Map<'a> { let show_towns = self.global_state.settings.interface.map_show_towns; let show_dungeons = self.global_state.settings.interface.map_show_dungeons; let show_castles = self.global_state.settings.interface.map_show_castles; + let show_bridges = self.global_state.settings.interface.map_show_bridges; let show_caves = self.global_state.settings.interface.map_show_caves; let show_trees = self.global_state.settings.interface.map_show_trees; let show_peaks = self.global_state.settings.interface.map_show_peaks; @@ -584,9 +588,43 @@ impl<'a> Widget for Map<'a> { .graphics_for(state.ids.show_castles_box) .color(TEXT_COLOR) .set(state.ids.show_castles_text, ui); + // Bridges + Image::new(self.imgs.mmap_site_bridge) + .down_from(state.ids.show_castles_img, 10.0) + .w_h(20.0, 20.0) + .set(state.ids.show_bridges_img, ui); + if Button::image(if show_bridges { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox + }) + .w_h(18.0, 18.0) + .hover_image(if show_bridges { + self.imgs.checkbox_checked_mo + } else { + self.imgs.checkbox_mo + }) + .press_image(if show_bridges { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox_press + }) + .right_from(state.ids.show_bridges_img, 10.0) + .set(state.ids.show_bridges_box, ui) + .was_clicked() + { + events.push(Event::SettingsChange(MapShowBridges(!show_bridges))); + } + Text::new(&i18n.get_msg("hud-map-bridge")) + .right_from(state.ids.show_bridges_box, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_bridges_box) + .color(TEXT_COLOR) + .set(state.ids.show_bridges_text, ui); // Dungeons Image::new(self.imgs.mmap_site_dungeon) - .down_from(state.ids.show_castles_img, 10.0) + .down_from(state.ids.show_bridges_img, 10.0) .w_h(20.0, 20.0) .set(state.ids.show_dungeons_img, ui); if Button::image(if show_dungeons { @@ -882,6 +920,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Tree => i18n.get_msg("hud-map-tree"), SiteKind::Gnarling => i18n.get_msg("hud-map-gnarling"), SiteKind::ChapelSite => i18n.get_msg("hud-map-chapel_Site"), + SiteKind::Bridge => i18n.get_msg("hud-map-bridge"), }); let (difficulty, desc) = match &site.kind { SiteKind::Town => (None, i18n.get_msg("hud-map-town")), @@ -907,6 +946,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Tree => (None, i18n.get_msg("hud-map-tree")), SiteKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")), SiteKind::ChapelSite => (Some(3), i18n.get_msg("hud-map-chapel_site")), + SiteKind::Bridge => (None, i18n.get_msg("hud-map-bridge")), }; let desc = desc.into_owned() + &get_site_economy(site_rich); let site_btn = Button::image(match &site.kind { @@ -921,6 +961,7 @@ impl<'a> Widget for Map<'a> { 5 => self.imgs.mmap_site_mindflayer, _ => self.imgs.mmap_site_dungeon, }, + SiteKind::Bridge => self.imgs.mmap_site_bridge, }) .x_y_position_relative_to( state.ids.map_layers[0], @@ -940,6 +981,7 @@ impl<'a> Widget for Map<'a> { 5 => self.imgs.mmap_site_mindflayer_hover, _ => self.imgs.mmap_site_dungeon_hover, }, + SiteKind::Bridge => self.imgs.mmap_site_bridge_hover, }) .image_color(UI_HIGHLIGHT_0.alpha(fade)) .with_tooltip( @@ -962,6 +1004,7 @@ impl<'a> Widget for Map<'a> { }, SiteKind::Cave => TEXT_COLOR, SiteKind::Tree => TEXT_COLOR, + SiteKind::Bridge => TEXT_COLOR, }, ); @@ -982,6 +1025,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Castle => show_castles, SiteKind::Cave => show_caves, SiteKind::Tree => show_trees, + SiteKind::Bridge => show_bridges, }; if show_site { let tooltip_visible = site_btn.set_ext(state.ids.mmap_site_icons[i], ui).1; @@ -1054,6 +1098,11 @@ impl<'a> Widget for Map<'a> { dif_img.set(state.ids.site_difs[i], ui) } }, + SiteKind::Bridge => { + if show_bridges { + dif_img.set(state.ids.site_difs[i], ui) + } + }, } handle_widget_mouse_events( diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 79a9993384..b8d83c3048 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -698,6 +698,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Cave => None, SiteKind::Tree => None, SiteKind::Gnarling => Some(0), + SiteKind::Bridge => None, }; Image::new(match &site.kind { @@ -708,6 +709,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Cave => self.imgs.mmap_site_cave_bg, SiteKind::Tree => self.imgs.mmap_site_tree, SiteKind::Gnarling => self.imgs.mmap_site_gnarling_bg, + SiteKind::Bridge => self.imgs.mmap_site_bridge_bg, }) .x_y_position_relative_to( state.ids.map_layers[0], @@ -733,6 +735,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Cave => self.imgs.mmap_site_cave, SiteKind::Tree => self.imgs.mmap_site_tree, SiteKind::Gnarling => self.imgs.mmap_site_gnarling, + SiteKind::Bridge => self.imgs.mmap_site_bridge, }) .middle_of(state.ids.mmap_site_icons_bgs[i]) .w_h(20.0, 20.0) diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index f2778abcbc..2ad2e94078 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -140,6 +140,7 @@ pub enum Interface { MapShowTowns(bool), MapShowDungeons(bool), MapShowCastles(bool), + MapShowBridges(bool), MapShowCaves(bool), MapShowTrees(bool), MapShowPeaks(bool), @@ -632,6 +633,9 @@ impl SettingsChange { Interface::MapShowCastles(map_show_castles) => { settings.interface.map_show_castles = map_show_castles; }, + Interface::MapShowBridges(map_show_bridges) => { + settings.interface.map_show_bridges = map_show_bridges; + }, Interface::MapShowCaves(map_show_caves) => { settings.interface.map_show_caves = map_show_caves; }, diff --git a/voxygen/src/settings/interface.rs b/voxygen/src/settings/interface.rs index 716e0b6038..166687c9c2 100644 --- a/voxygen/src/settings/interface.rs +++ b/voxygen/src/settings/interface.rs @@ -38,6 +38,7 @@ pub struct InterfaceSettings { pub map_show_towns: bool, pub map_show_dungeons: bool, pub map_show_castles: bool, + pub map_show_bridges: bool, pub loading_tips: bool, pub map_show_caves: bool, pub map_show_trees: bool, @@ -83,6 +84,7 @@ impl Default for InterfaceSettings { map_show_towns: true, map_show_dungeons: true, map_show_castles: false, + map_show_bridges: false, loading_tips: true, map_show_caves: true, map_show_trees: false, diff --git a/world/Cargo.toml b/world/Cargo.toml index ad4d12b6ce..6088ab03df 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -35,7 +35,7 @@ packed_simd = { package = "packed_simd_2", version = "0.3.8", optional = true } rayon = "1.5" serde = { version = "1.0.110", features = ["derive"] } ron = { version = "0.8", default-features = false } -# inline_tweak = "1.0.2" +inline_tweak = "1.0.2" kiddo = "0.2" strum = "0.24.0" diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 67938150b4..1d365d86e3 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -53,6 +53,8 @@ pub struct Civs { /// (3) we have 8-byte keys (for which FxHash is fastest). pub track_map: DHashMap, DHashMap, Id>>, + pub bridges: DHashMap, (Vec2, Id)>, + pub sites: Store, pub caves: Store, } @@ -187,6 +189,10 @@ impl Civs { SiteKind::GiantTree => (12i32, 8.0), SiteKind::Gnarling => (16i32, 10.0), SiteKind::Citadel => (16i32, 0.0), + SiteKind::Bridge(start, end) => { + let e = (end - start).map(|e| e.abs()).reduce_max(); + ((e + 1) / 2, ((e + 1) / 2) as f32 / 2.0) + }, }; let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { @@ -299,6 +305,12 @@ impl Civs { &mut rng, wpos, )), + SiteKind::Bridge(a, b) => WorldSite::bridge(site2::Site::generate_bridge( + &Land::from_sim(ctx.sim), + &mut rng, + *a, + *b, + )), }); sim_site.site_tmp = Some(site); let site_ref = &index.sites[site]; @@ -980,12 +992,21 @@ impl Civs { ) -> Id { const SITE_AREA: Range = 1..4; //64..256; - let place = match ctx.sim.get(loc).and_then(|site| site.place) { - Some(place) => place, - None => self.establish_place(ctx, loc, SITE_AREA), - }; + fn establish_site( + civs: &mut Civs, + ctx: &mut GenCtx, + loc: Vec2, + site_fn: impl FnOnce(Id) -> Site, + ) -> Id { + let place = match ctx.sim.get(loc).and_then(|site| site.place) { + Some(place) => place, + None => civs.establish_place(ctx, loc, SITE_AREA), + }; - let site = self.sites.insert(site_fn(place)); + civs.sites.insert(site_fn(place)) + } + + let site = establish_site(self, ctx, loc, site_fn); // Find neighbors const MAX_NEIGHBOR_DISTANCE: f32 = 2000.0; @@ -1017,7 +1038,12 @@ impl Civs { { for (nearby, _) in nearby.into_iter().take(5) { // Find a novel path - if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) { + if let Some((path, cost)) = find_path( + ctx, + |start| self.bridges.get(&start).map(|(end, _)| *end), + loc, + self.sites.get(nearby).center, + ) { // Find a path using existing paths if self .route_between(site, nearby) @@ -1027,16 +1053,55 @@ impl Civs { { // Write the track to the world as a path for locs in path.nodes().windows(3) { - let to_prev_idx = NEIGHBORS + let mut randomize_offset = false; + if let Some((i, _)) = NEIGHBORS .iter() .enumerate() .find(|(_, dir)| **dir == locs[0] - locs[1]) - .expect("Track locations must be neighbors") - .0; - let to_next_idx = NEIGHBORS + { + ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |= + 1 << ((i as u8 + 4) % 8); + ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= + 1 << (i as u8); + randomize_offset = true; + } + + if let Some((i, _)) = NEIGHBORS .iter() .enumerate() .find(|(_, dir)| **dir == locs[2] - locs[1]) + { + ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= + 1 << ((i as u8 + 4) % 8); + ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= + 1 << (i as u8); + randomize_offset = true; + } else if !self.bridges.contains_key(&locs[1]) { + let center = (locs[1] + locs[2]) / 2; + let id = + establish_site(self, &mut ctx.reseed(), center, move |place| { + Site { + kind: SiteKind::Bridge(locs[1], locs[2]), + site_tmp: None, + center, + place, + } + }); + self.bridges.insert(locs[1], (locs[2], id)); + self.bridges.insert(locs[2], (locs[1], id)); + } + /* + let to_prev_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == (locs[0] - locs[1]).map(|e| e.signum())) + .expect("Track locations must be neighbors") + .0; + + let to_next_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == (locs[2] - locs[1]).map(|e| e.signum())) .expect("Track locations must be neighbors") .0; @@ -1047,8 +1112,14 @@ impl Civs { let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); chunk.path.0.neighbors |= (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); - chunk.path.0.offset = - Vec2::new(ctx.rng.gen_range(-16..17), ctx.rng.gen_range(-16..17)); + */ + if randomize_offset { + let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); + chunk.path.0.offset = Vec2::new( + ctx.rng.gen_range(-16..17), + ctx.rng.gen_range(-16..17), + ); + } } // Take note of the track @@ -1125,21 +1196,25 @@ impl Civs { /// Attempt to find a path between two locations fn find_path( ctx: &mut GenCtx, + get_bridge: impl Fn(Vec2) -> Option>, a: Vec2, b: Vec2, ) -> Option<(Path>, f32)> { const MAX_PATH_ITERS: usize = 100_000; let sim = &ctx.sim; let heuristic = move |l: &Vec2| (l.distance_squared(b) as f32).sqrt(); + let get_bridge = &get_bridge; let neighbors = |l: &Vec2| { let l = *l; NEIGHBORS .iter() - .filter(move |dir| walk_in_dir(sim, l, **dir).is_some()) - .map(move |dir| l + *dir) + .filter_map(move |dir| walk_in_dir(sim, get_bridge, l, *dir)) + .map(move |(p, _)| p) + }; + let transition = |a: &Vec2, b: &Vec2| { + 1.0 + walk_in_dir(sim, get_bridge, *a, (*b - *a).map(|e| e.signum())) + .map_or(10000.0, |(_, cost)| cost) }; - let transition = - |a: &Vec2, b: &Vec2| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let satisfied = |l: &Vec2| *l == b; // We use this hasher (FxHasher64) because // (1) we don't care about DDOS attacks (ruling out SipHash); @@ -1160,23 +1235,31 @@ fn find_path( /// Return Some if travel between a location and a chunk next to it is permitted /// If permitted, the approximate relative const of traversal is given // (TODO: by whom?) -fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { - if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) { +fn walk_in_dir( + sim: &WorldSim, + get_bridge: impl Fn(Vec2) -> Option>, + a: Vec2, + dir: Vec2, +) -> Option<(Vec2, f32)> { + if let Some(p) = get_bridge(a).filter(|p| (a - p).map(|e| e.signum()) == dir) { + Some((p, 0.0)) + } else if loc_suitable_for_walking(sim, a + dir) { let a_chunk = sim.get(a)?; let b_chunk = sim.get(a + dir)?; let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 5.0).powi(2); - let water_cost = if b_chunk.river.near_water() { - 50.0 - } else { - 0.0 - } + (b_chunk.water_alt - b_chunk.alt + 8.0).clamped(0.0, 8.0) * 3.0; // Try not to path swamps / tidal areas + + let water_cost = (b_chunk.water_alt - b_chunk.alt + 8.0).clamped(0.0, 8.0) * 3.0; // Try not to path swamps / tidal areas let wild_cost = if b_chunk.path.0.is_way() { 0.0 // Traversing existing paths has no additional cost! } else { 3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics }; - Some(1.0 + hill_cost + water_cost + wild_cost) + Some((a + dir, 1.0 + hill_cost + water_cost + wild_cost)) + } else if dir.x == 0 || dir.y == 0 { + (4..=5).find_map(|i| { + loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, i as f32 * 30.0)) + }) } else { None } @@ -1184,8 +1267,14 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { /// Return true if a position is suitable for walking on fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2) -> bool { - if let Some(chunk) = sim.get(loc) { - !chunk.river.is_ocean() && !chunk.river.is_lake() && !chunk.near_cliffs() + if sim.get(loc).is_some() { + NEIGHBORS + .iter() + .find(|n| { + sim.get(loc + *n) + .map_or(false, |chunk| chunk.river.near_water()) + }) + .is_none() } else { false } @@ -1312,6 +1401,7 @@ pub enum SiteKind { GiantTree, Gnarling, Citadel, + Bridge(Vec2, Vec2), } impl SiteKind { @@ -1431,6 +1521,8 @@ impl SiteKind { && has_building_materials && industry_score > score_threshold && warm_or_firewood + // Because of how the algorithm for site2 towns work, they have to start on land. + && on_land() }; match self { SiteKind::Gnarling => { @@ -1495,6 +1587,7 @@ impl SiteKind { }, SiteKind::Dungeon => on_land(), SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(6.7), + SiteKind::Bridge(_, _) => true, } }) } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d78c4cbf84..6e3e067d5c 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1194,7 +1194,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { marble, marble_mid, marble_small, - rock_density, + rock_density: if spawn_rules.trees { rock_density } else { 0.0 }, temp, humidity, spawn_rate, diff --git a/world/src/lib.rs b/world/src/lib.rs index 6c12ecb1b0..d225516fcc 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -158,6 +158,7 @@ impl World { civ::SiteKind::Gnarling => world_msg::SiteKind::Gnarling, civ::SiteKind::ChapelSite => world_msg::SiteKind::ChapelSite, civ::SiteKind::Citadel => world_msg::SiteKind::Castle, + civ::SiteKind::Bridge(_, _) => world_msg::SiteKind::Bridge, }, wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), } diff --git a/world/src/site/economy/context.rs b/world/src/site/economy/context.rs index 175761efdf..b9e427c363 100644 --- a/world/src/site/economy/context.rs +++ b/world/src/site/economy/context.rs @@ -130,6 +130,7 @@ impl Environment { SiteKind::GiantTree(_) => (), SiteKind::Gnarling(_) => {}, SiteKind::ChapelSite(_) => {}, + SiteKind::Bridge(_) => {}, } } if towns.valid() { diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 1b0bf45efd..0c65c6280f 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -73,6 +73,7 @@ pub enum SiteKind { ChapelSite(site2::Site), GiantTree(site2::Site), Gnarling(site2::Site), + Bridge(site2::Site), } impl Site { @@ -153,6 +154,13 @@ impl Site { } } + pub fn bridge(b: site2::Site) -> Self { + Self { + kind: SiteKind::Bridge(b), + economy: Economy::default(), + } + } + pub fn radius(&self) -> f32 { match &self.kind { SiteKind::Settlement(s) => s.radius(), @@ -166,6 +174,7 @@ impl Site { SiteKind::Tree(t) => t.radius(), SiteKind::GiantTree(gt) => gt.radius(), SiteKind::Gnarling(g) => g.radius(), + SiteKind::Bridge(b) => b.radius(), } } @@ -182,6 +191,7 @@ impl Site { SiteKind::Tree(t) => t.origin, SiteKind::GiantTree(gt) => gt.origin, SiteKind::Gnarling(g) => g.origin, + SiteKind::Bridge(b) => b.origin, } } @@ -198,6 +208,7 @@ impl Site { SiteKind::Tree(t) => t.spawn_rules(wpos), SiteKind::GiantTree(gt) => gt.spawn_rules(wpos), SiteKind::Gnarling(g) => g.spawn_rules(wpos), + SiteKind::Bridge(b) => b.spawn_rules(wpos), } } @@ -214,6 +225,7 @@ impl Site { SiteKind::Tree(_) => "Giant Tree", SiteKind::GiantTree(gt) => gt.name(), SiteKind::Gnarling(g) => g.name(), + SiteKind::Bridge(b) => b.name(), } } @@ -249,6 +261,7 @@ impl Site { SiteKind::Tree(t) => t.render(canvas, dynamic_rng), SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng), SiteKind::Gnarling(g) => g.render(canvas, dynamic_rng), + SiteKind::Bridge(b) => b.render(canvas, dynamic_rng), } } @@ -278,6 +291,7 @@ impl Site { SiteKind::Tree(_) => {}, SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement), SiteKind::Gnarling(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), + SiteKind::Bridge(b) => b.apply_supplement(dynamic_rng, wpos2d, supplement), } } diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 406ff42161..b298fe6f03 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -1052,6 +1052,122 @@ impl Painter { } } + /// ```text + /// ___ + /// / /\ + /// /__/ | + /// / \ | + /// | | / + /// |_____|/ + /// ``` + pub fn vault(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { + let aabr = Aabr::from(aabb); + let a = dir.select_aabr(aabr); + let b = (-dir).select_aabr(aabr); + let dmin = a.min(b); + let dmax = a.max(b); + let length = dmax - dmin; + + let c = dir.rotated_cw().select_aabr(aabr); + let d = dir.rotated_ccw().select_aabr(aabr); + let omin = c.min(d); + let omax = c.max(d); + let diameter = omax - omin; + let radius = (diameter + 1) / 2; + let min = (a * dir.to_vec2() + c * dir.orthogonal().to_vec2()).with_z(aabb.max.z); + + self.cylinder( + Aabb { + min, + max: (a * dir.to_vec2() + + diameter * (-dir).to_vec2() + + d * dir.orthogonal().to_vec2()) + .with_z(aabb.max.z + length), + } + .made_valid(), + ) + .rotate_about(dir.abs().from_z_mat3(), min) + .without(self.aabb(Aabb { + min: aabb.min.with_z(aabb.min.z - radius), + max: aabb.max.with_z(aabb.min.z), + })) + .union(self.aabb(Aabb { + min: aabb.min, + max: aabb.max.with_z(aabb.max.z - radius), + })) + } + + /// Place aabbs around another aabb in a symmetric and distributed manner. + pub fn aabbs_around_aabb(&self, aabb: Aabb, size: i32, offset: i32) -> PrimitiveRef { + let pillar = self.aabb(Aabb { + min: (aabb.min.xy() - 1).with_z(aabb.min.z), + max: (aabb.min.xy() + size - 1).with_z(aabb.max.z), + }); + + let true_offset = offset + size; + + let size_x = aabb.max.x - aabb.min.x; + let size_y = aabb.max.y - aabb.min.y; + + let num_aabbs = ((size_x + 1) / 2) / true_offset; + let x = pillar.repeat(Vec3::new(true_offset, 0, 0), num_aabbs as u32 + 1); + + let num_aabbs = ((size_y + 1) / 2) / true_offset; + let y = pillar.repeat(Vec3::new(0, true_offset, 0), num_aabbs as u32 + 1); + let center = aabb.as_::().center(); + let shape = x.union(y); + let shape = + shape.union(shape.rotate_about(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, -1), center)); + shape.union(shape.rotate_about(Mat3::new(0, 1, 0, -1, 0, 0, 0, 0, 1), center)) + } + + pub fn staircase_in_aabb( + &self, + aabb: Aabb, + thickness: i32, + start_dir: Dir, + ) -> PrimitiveRef { + let mut forward = start_dir; + let mut z = aabb.max.z - 1; + let aabr = Aabr::from(aabb); + + let mut prim = self.empty(); + + while z > aabb.min.z { + let right = forward.rotated_cw(); + let fc = forward.select_aabr(aabr); + let corner = + fc * forward.abs().to_vec2() + right.select_aabr(aabr) * right.abs().to_vec2(); + let aabb = Aabb { + min: corner.with_z(z), + max: (corner - (forward.to_vec2() + right.to_vec2()) * thickness).with_z(z + 1), + } + .made_valid(); + + let stair_len = ((fc - (-forward).select_aabr(aabr)).abs() - thickness * 2).max(1) + 1; + + let stairs = self.ramp( + Aabb { + min: (corner - right.to_vec2() * (stair_len + thickness)) + .with_z(z - stair_len), + max: (corner - right.to_vec2() * (thickness - 1) - forward.to_vec2() * thickness) + .with_z(z + 1), + } + .made_valid(), + stair_len + 1, + right, + ); + + prim = prim + .union(self.aabb(aabb)) + .union(stairs.without(stairs.translate(Vec3::new(0, 0, -2)))); + + z -= stair_len; + forward = forward.rotated_ccw(); + } + prim + } + /// The area that the canvas is currently rendering. pub fn render_aabr(&self) -> Aabr { self.render_area } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 0ac386b461..89a46f7a30 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -988,6 +988,68 @@ impl Site { site } + pub fn generate_bridge( + land: &Land, + rng: &mut impl Rng, + start: Vec2, + end: Vec2, + ) -> Self { + let mut rng = reseed(rng); + let start = TerrainChunkSize::center_wpos(start); + let end = TerrainChunkSize::center_wpos(end); + let origin = (start + end) / 2; + + let mut site = Site { + origin, + name: format!("Bridge of {}", NameGen::location(&mut rng).generate_town()), + ..Site::default() + }; + + let start_tile = site.wpos_tile_pos(start); + let end_tile = site.wpos_tile_pos(end); + + let width = 1; + + let orth = (start_tile - end_tile).yx().map(|dir| dir.signum().abs()); + + let aabr = Aabr { + min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width, + max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width, + }; + + site.create_road( + land, + &mut rng, + aabr.min - 1, + aabr.min - 1 + orth * (3 + width * 2), + 2, + ); + site.create_road( + land, + &mut rng, + aabr.max + 1, + aabr.max + 1 - orth * (3 + width * 2), + 2, + ); + + let bridge = plot::Bridge::generate(land, &mut rng, &site, start_tile, end_tile, width); + + let plot = site.create_plot(Plot { + kind: PlotKind::Bridge(bridge), + root_tile: start_tile, + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + }); + + site.blit_aabr(aabr, Tile { + kind: TileKind::Building, + plot: Some(plot), + hard_alt: None, + }); + + site + } + pub fn wpos_tile_pos(&self, wpos2d: Vec2) -> Vec2 { (wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32)) } @@ -1245,6 +1307,7 @@ impl Site { desert_city_temple.render_collect(self, canvas) }, PlotKind::Citadel(citadel) => citadel.render_collect(self, canvas), + PlotKind::Bridge(bridge) => bridge.render_collect(self, canvas), _ => continue, }; diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 1e0513a9e6..96c4e5000f 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -1,3 +1,4 @@ +mod bridge; mod castle; mod citadel; mod cliff_tower; @@ -12,7 +13,7 @@ mod sea_chapel; mod workshop; pub use self::{ - castle::Castle, citadel::Citadel, cliff_tower::CliffTower, + bridge::Bridge, castle::Castle, citadel::Citadel, cliff_tower::CliffTower, desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple, dungeon::Dungeon, giant_tree::GiantTree, gnarling::GnarlingFortification, house::House, savannah_pit::SavannahPit, sea_chapel::SeaChapel, workshop::Workshop, @@ -66,4 +67,5 @@ pub enum PlotKind { CliffTower(CliffTower), Citadel(Citadel), SavannahPit(SavannahPit), + Bridge(Bridge), } diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs new file mode 100644 index 0000000000..8bc456363f --- /dev/null +++ b/world/src/site2/plot/bridge.rs @@ -0,0 +1,335 @@ +use super::*; +use crate::{site2::gen::PrimitiveTransform, Land}; +use common::terrain::{Block, BlockKind}; +use rand::prelude::*; +use vek::*; + +use inline_tweak::tweak; + +enum RoofKind { + Crenelated, +} + +enum BridgeKind { + Flat, + Tower, +} + +fn aabb(min: Vec3, max: Vec3) -> Aabb { + let aabb = Aabb { min, max }.made_valid(); + Aabb { + min: aabb.min, + max: aabb.max + 1, + } +} + +fn render_flat(bridge: &Bridge, painter: &Painter) { + let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + + let dz = bridge.end.z - bridge.start.z; + let orthogonal = bridge.dir.orthogonal().to_vec2(); + let forward = bridge.dir.to_vec2(); + let inset = 5; + + let len = (bridge.end.xy() - bridge.start.xy()) + .map(|e| e.abs()) + .reduce_max(); + let upset = bridge.end.z - bridge.start.z; + + let size = tweak!(8); + let hole = painter + .cylinder(aabb( + bridge.center.with_z(bridge.end.z - 3 - bridge.width * 2) - Vec2::broadcast(size), + bridge.center.with_z(bridge.end.z + 2) + Vec2::broadcast(size), + )) + .rotate_about( + bridge.dir.orthogonal().from_z_mat3(), + bridge + .center + .as_() + .with_z(bridge.end.z as f32 - 1.5 - bridge.width as f32), + ) + .scale( + bridge.dir.abs().to_vec3().as_() + * ((len as f32 - upset as f32 * tweak!(2.0)) / (size as f32 * 2.0) - 1.0) + + 1.0, + ); + + painter + .ramp( + aabb( + bridge.start.with_z(bridge.start.z - inset) + - orthogonal * bridge.width + - forward * inset, + bridge.start.with_z(bridge.end.z) + orthogonal * bridge.width + forward * dz, + ), + dz + inset + 1, + bridge.dir, + ) + .union( + painter + .aabb(aabb( + bridge.start.with_z(bridge.end.z - 3 - size) - orthogonal * bridge.width + + forward * dz, + bridge.start.with_z(bridge.end.z) + + orthogonal * bridge.width + + forward * forward * (bridge.end.xy() - bridge.start.xy()), + )) + .without(hole), + ) + .fill(rock); +} + +fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: RoofKind) { + let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + let tower_size = 5; + + let bridge_width = tower_size - 2; + + let orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); + let forward = bridge.dir.to_vec2(); + + let tower_height_extend = 10; + + let tower_end = bridge.end.z + tower_height_extend; + + let tower_aabr = Aabr { + min: bridge.start.xy() - tower_size, + max: bridge.start.xy() + tower_size, + }; + + let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs(); + + painter + .aabb(aabb( + tower_aabr.min.with_z(bridge.start.z - 5), + tower_aabr.max.with_z(tower_end), + )) + .fill(rock.clone()); + + painter + .aabb(aabb( + (tower_aabr.min + 1).with_z(bridge.start.z), + (tower_aabr.max - 1).with_z(tower_end - 1), + )) + .clear(); + + let c = bridge.start.xy() - forward * tower_size; + painter + .aabb(aabb( + (c - orthogonal).with_z(bridge.start.z), + (c + orthogonal).with_z(bridge.start.z + 2), + )) + .clear(); + + let ramp_height = 8; + + let ramp_aabb = aabb( + (c - forward - orthogonal).with_z(bridge.start.z - 1), + (c - forward * ramp_height + orthogonal).with_z(bridge.start.z + ramp_height - 2), + ); + + painter + .aabb(ramp_aabb) + .without(painter.ramp(ramp_aabb, ramp_height, -bridge.dir)) + .clear(); + + let c = bridge.start.xy() + forward * tower_size; + painter + .aabb(aabb( + (c - orthogonal).with_z(bridge.end.z), + (c + orthogonal).with_z(bridge.end.z + 2), + )) + .clear(); + + let stair_thickness = 2; + painter + .staircase_in_aabb( + aabb( + (tower_aabr.min + 1).with_z(bridge.start.z), + (tower_aabr.max - 1).with_z(bridge.end.z - 1), + ), + stair_thickness, + bridge.dir.rotated_ccw(), + ) + .fill(rock.clone()); + let aabr = bridge + .dir + .rotated_cw() + .split_aabr(tower_aabr, stair_thickness + 1)[1]; + + painter + .aabb(aabb( + aabr.min.with_z(bridge.end.z - 1), + aabr.max.with_z(bridge.end.z - 1), + )) + .fill(rock.clone()); + + painter + .aabb(aabb( + tower_aabr.center().with_z(bridge.start.z), + tower_aabr.center().with_z(bridge.end.z - 1), + )) + .fill(rock.clone()); + + let offset = tower_size * 2 - 2; + let d = tweak!(2); + let n = (bridge.end.z - bridge.start.z - d) / offset; + let p = (bridge.end.z - bridge.start.z - d) / n; + + for i in 1..=n { + let c = tower_aabr.center().with_z(bridge.start.z + i * p); + + for dir in Dir::ALL { + painter.rotated_sprite(c + dir.to_vec2(), SpriteKind::WallSconce, dir.sprite_ori()); + } + } + + painter.rotated_sprite( + (tower_aabr.center() + bridge.dir.to_vec2() * (tower_size - 1)) + .with_z(bridge.end.z + tower_height_extend / 2), + SpriteKind::WallLamp, + (-bridge.dir).sprite_ori(), + ); + + match roof_kind { + RoofKind::Crenelated => { + painter + .aabb(aabb( + (tower_aabr.min - 1).with_z(tower_end + 1), + (tower_aabr.max + 1).with_z(tower_end + 2), + )) + .fill(rock.clone()); + + painter + .aabbs_around_aabb( + aabb( + tower_aabr.min.with_z(tower_end + 3), + tower_aabr.max.with_z(tower_end + 3), + ), + 1, + 1, + ) + .fill(rock.clone()); + + painter + .aabb(aabb( + tower_aabr.min.with_z(tower_end + 2), + tower_aabr.max.with_z(tower_end + 2), + )) + .clear(); + + painter + .aabbs_around_aabb( + aabb( + (tower_aabr.min + 1).with_z(tower_end + 2), + (tower_aabr.max - 1).with_z(tower_end + 2), + ), + 1, + 4, + ) + .fill(Fill::Sprite(SpriteKind::FireBowlGround)); + }, + } + + let offset = 15; + let thickness = 3; + + let size = (offset - thickness) / 2; + + let n = len / offset; + let p = len / n; + + let offset = forward * p; + + let size = bridge_width * orthogonal + forward * size; + let start = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()); + painter + .aabb(aabb( + (start - orthogonal * bridge_width).with_z(bridge.center.z - 10), + bridge.end.with_z(bridge.end.z - 1) + orthogonal * bridge_width, + )) + .without( + painter + .vault( + aabb( + (start + offset - size).with_z(bridge.center.z - 10), + (start + offset + size).with_z(bridge.end.z - 3), + ), + orth_dir, + ) + .repeat(offset.with_z(0), n as u32), + ) + .fill(rock.clone()); + + let light_spacing = 10; + let n = len / light_spacing; + let p = len / n; + + let start = bridge.end; + let offset = -forward * p; + for i in 1..=n { + let c = start + i * offset; + + painter.sprite(c + orthogonal * bridge_width, SpriteKind::StreetLamp); + painter.sprite(c - orthogonal * bridge_width, SpriteKind::StreetLamp); + } +} + +pub struct Bridge { + start: Vec3, + end: Vec3, + center: Vec3, + dir: Dir, + kind: BridgeKind, + pub(crate) width: i32, +} + +impl Bridge { + pub fn generate( + land: &Land, + _rng: &mut impl Rng, + site: &Site, + start: Vec2, + end: Vec2, + width: i32, + ) -> Self { + let start = site.tile_center_wpos(start); + let end = site.tile_center_wpos(end); + let width = width * TILE_SIZE as i32 + TILE_SIZE as i32 / 2; + + let center = (start + end) / 2; + + let mut start = start.with_z(land.get_alt_approx(start) as i32); + let mut end = end.with_z(land.get_alt_approx(end) as i32); + if start.z > end.z { + std::mem::swap(&mut start, &mut end); + } + + let center = center.with_z(land.get_alt_approx(center) as i32); + + Self { + start, + end, + center, + dir: Dir::from_vector(end.xy() - start.xy()), + kind: if end.z - start.z > 10 { + BridgeKind::Tower + } else { + BridgeKind::Flat + }, + width, + } + } +} + +impl Structure for Bridge { + fn render(&self, _site: &Site, _land: &Land, painter: &Painter) { + match self.kind { + BridgeKind::Flat => render_flat(&self, painter), + BridgeKind::Tower => render_tower(&self, painter, RoofKind::Crenelated), + } + } +} diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 7a15e5f356..4b0e3ed3df 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -13,6 +13,9 @@ pub enum Dir { } impl Dir { + + pub const ALL: [Dir; 4] = [Dir::X, Dir::Y, Dir::NegX, Dir::NegY]; + pub fn choose(rng: &mut impl Rng) -> Dir { match rng.gen_range(0..4) { 0 => Dir::X, @@ -64,6 +67,30 @@ impl Dir { } } + #[must_use] + pub fn orthogonal(self) -> Dir { + match self { + Dir::X | Dir::NegX => Dir::Y, + Dir::Y | Dir::NegY => Dir::X, + } + } + + #[must_use] + pub fn abs(self) -> Dir { + match self { + Dir::X | Dir::NegX => Dir::X, + Dir::Y | Dir::NegY => Dir::Y, + } + } + + #[must_use] + pub fn signum(self) -> i32 { + match self { + Dir::X | Dir::Y => 1, + Dir::NegX | Dir::NegY => -1, + } + } + pub fn to_vec2(self) -> Vec2 { match self { Dir::X => Vec2::new(1, 0), @@ -105,7 +132,7 @@ impl Dir { /// /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); /// ``` - pub fn to_mat3x3(self) -> Mat3 { + pub fn to_mat3(self) -> Mat3 { match self { Dir::X => Mat3::new(1, 0, 0, 0, 1, 0, 0, 0, 1), Dir::NegX => Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1), @@ -114,6 +141,15 @@ impl Dir { } } + pub fn from_z_mat3(self) -> Mat3 { + match self { + Dir::X => Mat3::new(0, 0, -1, 0, 1, 0, 1, 0, 0), + Dir::NegX => Mat3::new(0, 0, 1, 0, 1, 0, -1, 0, 0), + Dir::Y => Mat3::new(1, 0, 0, 0, 0, -1, 0, 1, 0), + Dir::NegY => Mat3::new(1, 0, 0, 0, 0, 1, 0, -1, 0), + } + } + /// Translates this direction to worldspace as if it was relative to the /// other direction #[must_use] @@ -131,6 +167,90 @@ impl Dir { /// Is this direction parallel to y pub fn is_y(self) -> bool { matches!(self, Dir::Y | Dir::NegY) } + + /// Returns the component that the direction is parallell to + pub fn select(self, vec: Vec2) -> i32 { + match self { + Dir::X | Dir::NegX => vec.x, + Dir::Y | Dir::NegY => vec.y, + } + } + + /// Select one component the direction is parallel to from vec and select + /// the other component from other + pub fn select_with(self, vec: Vec2, other: Vec2) -> Vec2 { + match self { + Dir::X | Dir::NegX => Vec2::new(vec.x, other.y), + Dir::Y | Dir::NegY => Vec2::new(other.x, vec.y), + } + } + + /// Returns the side of an aabr that the direction is pointing to + pub fn select_aabr(self, aabr: Aabr) -> i32 { + match self { + Dir::X => aabr.max.x, + Dir::NegX => aabr.min.x, + Dir::Y => aabr.max.y, + Dir::NegY => aabr.min.y, + } + } + + /// Select one component from the side the direction is pointing to from + /// aabr and select the other component from other + pub fn select_aabr_with(self, aabr: Aabr, other: Vec2) -> Vec2 { + match self { + Dir::X => Vec2::new(aabr.max.x, other.y), + Dir::NegX => Vec2::new(aabr.min.x, other.y), + Dir::Y => Vec2::new(other.x, aabr.max.y), + Dir::NegY => Vec2::new(other.x, aabr.min.y), + } + } + + /// The equivelant sprite direction of the direction + pub fn sprite_ori(self) -> u8 { + match self { + Dir::X => 2, + Dir::NegX => 6, + Dir::Y => 4, + Dir::NegY => 0, + } + } + + pub fn split_aabr(self, aabr: Aabr, offset: i32) -> [Aabr; 2] { + match self { + Dir::X => aabr.split_at_x(aabr.min.x + offset), + Dir::Y => aabr.split_at_y(aabr.min.y + offset), + Dir::NegX => { + let res = aabr.split_at_x(aabr.max.x - offset); + [res[1], res[0]] + }, + Dir::NegY => { + let res = aabr.split_at_y(aabr.max.y - offset); + [res[1], res[0]] + }, + } + } + + pub fn extend_aabr(self, aabr: Aabr, amount: i32) -> Aabr { + match self { + Dir::X => Aabr { + min: aabr.min, + max: aabr.max + Vec2::new(amount, 0), + }, + Dir::Y => Aabr { + min: aabr.min, + max: aabr.max + Vec2::new(0, amount), + }, + Dir::NegX => Aabr { + min: aabr.min - Vec2::new(amount, 0), + max: aabr.max, + }, + Dir::NegY => Aabr { + min: aabr.min - Vec2::new(0, amount), + max: aabr.max, + }, + } + } } impl std::ops::Neg for Dir { From 3bb66cf2ff3f013c75b9963483655bd08f469c4b Mon Sep 17 00:00:00 2001 From: IsseW Date: Mon, 26 Sep 2022 16:57:57 +0200 Subject: [PATCH 02/14] add hipped roof --- world/src/site2/plot/bridge.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 8bc456363f..4c6afd711a 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -8,11 +8,12 @@ use inline_tweak::tweak; enum RoofKind { Crenelated, + Hipped, } enum BridgeKind { Flat, - Tower, + Tower(RoofKind), } fn aabb(min: Vec3, max: Vec3) -> Aabb { @@ -80,8 +81,10 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { .fill(rock); } -fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: RoofKind) { +fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(40, 28, 20))); + let tower_size = 5; let bridge_width = tower_size - 2; @@ -232,6 +235,9 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: RoofKind) { ) .fill(Fill::Sprite(SpriteKind::FireBowlGround)); }, + RoofKind::Hipped => { + painter.pyramid(aabb((tower_aabr.min - 1).with_z(tower_end + 1), (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size))).fill(wood.clone()); + }, } let offset = 15; @@ -290,7 +296,7 @@ pub struct Bridge { impl Bridge { pub fn generate( land: &Land, - _rng: &mut impl Rng, + rng: &mut impl Rng, site: &Site, start: Vec2, end: Vec2, @@ -316,7 +322,10 @@ impl Bridge { center, dir: Dir::from_vector(end.xy() - start.xy()), kind: if end.z - start.z > 10 { - BridgeKind::Tower + BridgeKind::Tower(match rng.gen_range(0..=2) { + 0 => RoofKind::Crenelated, + _ => RoofKind::Hipped, + }) } else { BridgeKind::Flat }, @@ -327,9 +336,9 @@ impl Bridge { impl Structure for Bridge { fn render(&self, _site: &Site, _land: &Land, painter: &Painter) { - match self.kind { + match &self.kind { BridgeKind::Flat => render_flat(&self, painter), - BridgeKind::Tower => render_tower(&self, painter, RoofKind::Crenelated), + BridgeKind::Tower(roof) => render_tower(&self, painter, roof), } } } From 78e227d317efd931a8a364d8f43fc8d0f6785a0f Mon Sep 17 00:00:00 2001 From: IsseW Date: Tue, 27 Sep 2022 14:58:15 +0200 Subject: [PATCH 03/14] short bridges --- world/src/civ/mod.rs | 128 ++++++++++---------- world/src/land.rs | 11 +- world/src/site2/gen.rs | 7 +- world/src/site2/mod.rs | 61 ++++++++-- world/src/site2/plot/bridge.rs | 206 ++++++++++++++++++++++++++++----- world/src/site2/util/mod.rs | 1 - 6 files changed, 315 insertions(+), 99 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 1d365d86e3..782885cac0 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -8,7 +8,7 @@ use crate::{ site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree}, site2, util::{attempt, seed_expan, DHashMap, NEIGHBORS}, - Index, Land, + Index, IndexRef, Land, }; use common::{ astar::Astar, @@ -252,65 +252,73 @@ impl Civs { }); let mut rng = ctx.reseed().rng; - let site = index.sites.insert(match &sim_site.kind { - SiteKind::Settlement => { - WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng)) - }, - SiteKind::Dungeon => WorldSite::dungeon(site2::Site::generate_dungeon( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::Castle => { - WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng)) - }, - SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::SavannahPit => WorldSite::savannah_pit( - site2::Site::generate_savannah_pit(&Land::from_sim(ctx.sim), &mut rng, wpos), - ), - SiteKind::DesertCity => WorldSite::desert_city(site2::Site::generate_desert_city( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::Tree => { - WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng)) - }, - SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::Gnarling => WorldSite::gnarling(site2::Site::generate_gnarling( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::ChapelSite => WorldSite::chapel_site(site2::Site::generate_chapel_site( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::Citadel => WorldSite::gnarling(site2::Site::generate_citadel( - &Land::from_sim(ctx.sim), - &mut rng, - wpos, - )), - SiteKind::Bridge(a, b) => WorldSite::bridge(site2::Site::generate_bridge( - &Land::from_sim(ctx.sim), - &mut rng, - *a, - *b, - )), + let site = index.sites.insert({ + let index_ref = IndexRef { + colors: &index.colors(), + features: &index.features(), + index, + }; + match &sim_site.kind { + SiteKind::Settlement => { + WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng)) + }, + SiteKind::Dungeon => WorldSite::dungeon(site2::Site::generate_dungeon( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::Castle => { + WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng)) + }, + SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::SavannahPit => { + WorldSite::savannah_pit(site2::Site::generate_savannah_pit( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )) + }, + SiteKind::DesertCity => WorldSite::desert_city( + site2::Site::generate_desert_city(&Land::from_sim(ctx.sim), &mut rng, wpos), + ), + SiteKind::Tree => { + WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng)) + }, + SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::Gnarling => WorldSite::gnarling(site2::Site::generate_gnarling( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::ChapelSite => WorldSite::chapel_site( + site2::Site::generate_chapel_site(&Land::from_sim(ctx.sim), &mut rng, wpos), + ), + SiteKind::Citadel => WorldSite::gnarling(site2::Site::generate_citadel( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), + SiteKind::Bridge(a, b) => WorldSite::bridge(site2::Site::generate_bridge( + &Land::from_sim(ctx.sim), + index_ref, + &mut rng, + *a, + *b, + )), + } }); sim_site.site_tmp = Some(site); let site_ref = &index.sites[site]; diff --git a/world/src/land.rs b/world/src/land.rs index f6bad2bd0e..1c9afd06b7 100644 --- a/world/src/land.rs +++ b/world/src/land.rs @@ -1,4 +1,4 @@ -use crate::sim; +use crate::{column::ColumnGen, sim, util::Sampler, ColumnSample, IndexRef}; use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use vek::*; @@ -45,4 +45,13 @@ impl<'a> Land<'a> { ) -> Option<(f32, Vec2, sim::Path, Vec2)> { self.sim.and_then(|sim| sim.get_nearest_path(wpos)) } + + pub fn column_sample<'sample>( + &'sample self, + wpos: Vec2, + index: IndexRef<'sample>, + ) -> Option> { + self.sim + .and_then(|sim| ColumnGen::new(sim).get((wpos, index, None))) + } } diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index b298fe6f03..b7d8bba747 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -1148,9 +1148,10 @@ impl Painter { let stairs = self.ramp( Aabb { - min: (corner - right.to_vec2() * (stair_len + thickness)) - .with_z(z - stair_len), - max: (corner - right.to_vec2() * (thickness - 1) - forward.to_vec2() * thickness) + min: (corner - right.to_vec2() * (stair_len + thickness)).with_z(z - stair_len), + max: (corner + - right.to_vec2() * (thickness - 1) + - forward.to_vec2() * thickness) .with_z(z + 1), } .made_valid(), diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 89a46f7a30..44f310db54 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -13,7 +13,7 @@ use crate::{ sim::Path, site::{namegen::NameGen, SpawnRules}, util::{attempt, DHashSet, Grid, CARDINALS, SQUARE_4, SQUARE_9}, - Canvas, Land, + Canvas, IndexRef, Land, }; use common::{ astar::Astar, @@ -990,6 +990,7 @@ impl Site { pub fn generate_bridge( land: &Land, + index: IndexRef, rng: &mut impl Rng, start: Vec2, end: Vec2, @@ -1012,6 +1013,18 @@ impl Site { let orth = (start_tile - end_tile).yx().map(|dir| dir.signum().abs()); + let start_aabr = Aabr { + min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width, + max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width, + }; + + + let bridge = plot::Bridge::generate(land, index, &mut rng, &site, start_tile, end_tile); + + let start_tile = site.wpos_tile_pos(bridge.start.xy()); + let end_tile = site.wpos_tile_pos(bridge.end.xy()); + + let width = (bridge.width + TILE_SIZE as i32 / 2) / TILE_SIZE as i32; let aabr = Aabr { min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width, max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width, @@ -1020,20 +1033,18 @@ impl Site { site.create_road( land, &mut rng, - aabr.min - 1, - aabr.min - 1 + orth * (3 + width * 2), + bridge.dir.select_aabr_with(aabr, aabr.center()) + bridge.dir.to_vec2(), + bridge.dir.select_aabr_with(start_aabr, aabr.center()), 2, ); site.create_road( land, &mut rng, - aabr.max + 1, - aabr.max + 1 - orth * (3 + width * 2), + (-bridge.dir).select_aabr_with(aabr, aabr.center()) - bridge.dir.to_vec2(), + (-bridge.dir).select_aabr_with(start_aabr, aabr.center()), 2, ); - - let bridge = plot::Bridge::generate(land, &mut rng, &site, start_tile, end_tile, width); - + let plot = site.create_plot(Plot { kind: PlotKind::Bridge(bridge), root_tile: start_tile, @@ -1047,6 +1058,40 @@ impl Site { hard_alt: None, }); + + let size = (1.5 + rng.gen::().powf(5.0) * 1.0).round() as u32; + if let Some((aabr, door_tile, door_dir)) = attempt(32, || { + site.find_roadside_aabr( + &mut rng, + 4..(size + 1).pow(2), + Extent2::broadcast(size), + ) + }) { + let house = plot::House::generate( + land, + &mut reseed(&mut rng), + &site, + door_tile, + door_dir, + aabr, + ); + let house_alt = house.alt; + let plot = site.create_plot(Plot { + kind: PlotKind::House(house), + root_tile: aabr.center(), + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + }); + + site.blit_aabr(aabr, Tile { + kind: TileKind::Building, + plot: Some(plot), + hard_alt: Some(house_alt), + }); + } else { + site.make_plaza(land, &mut rng); + } + site } diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 4c6afd711a..e6bbbc80be 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -14,6 +14,7 @@ enum RoofKind { enum BridgeKind { Flat, Tower(RoofKind), + Short, } fn aabb(min: Vec3, max: Vec3) -> Aabb { @@ -24,6 +25,92 @@ fn aabb(min: Vec3, max: Vec3) -> Aabb { } } +fn render_short(bridge: &Bridge, painter: &Painter) { + let rock = Fill::Brick(BlockKind::Rock, Rgb::gray(70), 25); + let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(140))); + + let bridge_width = 3; + + let orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); + let forward = bridge.dir.to_vec2(); + + let len = (bridge.start.xy() - bridge.end.xy()) + .map(|e| e.abs()) + .reduce_max(); + let inset = 4; + + let height = bridge.end.z + len / 3 - inset; + + let side = orthogonal * bridge_width; + + let remove = painter.vault( + aabb( + (bridge.start.xy() - side + forward * inset).with_z(bridge.start.z), + (bridge.end.xy() + side - forward * inset).with_z(height), + ), + orth_dir, + ); + + let ramp_in = len / 3; + + let top = height + 2; + + let outset = 7; + + let up_ramp = |point: Vec3, dir: Dir, side_len: i32| { + let forward = dir.to_vec2(); + let side = dir.orthogonal().to_vec2() * side_len; + painter.ramp( + aabb( + (point.xy() - side - forward * (top - point.z - ramp_in + outset)) + .with_z(bridge.start.z), + (point.xy() + side + forward * ramp_in).with_z(top), + ), + top - point.z + 1 + outset, + dir, + ) + }; + + let bridge_prim = |side_len: i32| { + let side = orthogonal * side_len; + painter + .aabb(aabb( + (bridge.start.xy() - side + forward * ramp_in).with_z(bridge.start.z), + (bridge.end.xy() + side - forward * ramp_in).with_z(top), + )) + .union(up_ramp(bridge.start, bridge.dir, side_len).union(up_ramp( + bridge.end, + -bridge.dir, + side_len, + ))) + }; + + let b = bridge_prim(bridge_width); + + let t = 4; + b.union(painter.aabb(aabb( + (bridge.start.xy() - side - forward * (top - bridge.start.z - ramp_in + outset)).with_z(bridge.start.z - t), + (bridge.end.xy() + side + forward * (top - bridge.end.z - ramp_in + outset)).with_z(bridge.start.z), + ))) + .translate(Vec3::new(0, 0, t)) + .without(b) + .clear(); + + b.without(remove).fill(rock.clone()); + + let prim = bridge_prim(bridge_width + 1); + + prim.translate(Vec3::unit_z()) + .without(prim) + .without(painter.aabb(aabb( + bridge.start - side - forward * outset, + (bridge.end.xy() + side + forward * outset).with_z(top + 1), + ))) + .fill(light_rock); +} + fn render_flat(bridge: &Bridge, painter: &Painter) { let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); @@ -98,9 +185,10 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { let tower_end = bridge.end.z + tower_height_extend; + let tower_center = bridge.start.xy() + forward * tower_size; let tower_aabr = Aabr { - min: bridge.start.xy() - tower_size, - max: bridge.start.xy() + tower_size, + min: tower_center - tower_size, + max: tower_center + tower_size, }; let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs(); @@ -119,7 +207,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { )) .clear(); - let c = bridge.start.xy() - forward * tower_size; + let c = (-bridge.dir).select_aabr_with(tower_aabr, tower_aabr.center()); painter .aabb(aabb( (c - orthogonal).with_z(bridge.start.z), @@ -139,7 +227,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { .without(painter.ramp(ramp_aabb, ramp_height, -bridge.dir)) .clear(); - let c = bridge.start.xy() + forward * tower_size; + let c = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()); painter .aabb(aabb( (c - orthogonal).with_z(bridge.end.z), @@ -236,7 +324,12 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { .fill(Fill::Sprite(SpriteKind::FireBowlGround)); }, RoofKind::Hipped => { - painter.pyramid(aabb((tower_aabr.min - 1).with_z(tower_end + 1), (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size))).fill(wood.clone()); + painter + .pyramid(aabb( + (tower_aabr.min - 1).with_z(tower_end + 1), + (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size), + )) + .fill(wood.clone()); }, } @@ -251,11 +344,11 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { let offset = forward * p; let size = bridge_width * orthogonal + forward * size; - let start = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()); + let start = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()) + forward; painter .aabb(aabb( (start - orthogonal * bridge_width).with_z(bridge.center.z - 10), - bridge.end.with_z(bridge.end.z - 1) + orthogonal * bridge_width, + (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z - 1), )) .without( painter @@ -270,6 +363,13 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { ) .fill(rock.clone()); + painter + .aabb(aabb( + (start - orthogonal * bridge_width).with_z(bridge.end.z), + (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z + 5), + )) + .clear(); + let light_spacing = 10; let n = len / light_spacing; let p = len / n; @@ -285,51 +385,104 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { } pub struct Bridge { - start: Vec3, - end: Vec3, - center: Vec3, - dir: Dir, - kind: BridgeKind, + pub(crate) start: Vec3, + pub(crate) end: Vec3, pub(crate) width: i32, + pub(crate) dir: Dir, + center: Vec3, + kind: BridgeKind, } impl Bridge { pub fn generate( land: &Land, + index: IndexRef, rng: &mut impl Rng, site: &Site, start: Vec2, end: Vec2, - width: i32, ) -> Self { - let start = site.tile_center_wpos(start); - let end = site.tile_center_wpos(end); - let width = width * TILE_SIZE as i32 + TILE_SIZE as i32 / 2; + let start = site.tile_wpos(start); + let end = site.tile_wpos(end); - let center = (start + end) / 2; + let min_water_dist = 5; + let find_edge = |start: Vec2, end: Vec2| { + let mut test_start = start; + let dir = Dir::from_vector(end - start).to_vec2(); + let mut last_alt = if let Some(col) = land.column_sample(start, index) { + col.alt as i32 + } else { + return test_start.with_z(land.get_alt_approx(start) as i32); + }; + let mut step = 0; + loop { + if let Some(sample) = land.column_sample(test_start + step * dir, index) { + let alt = sample.alt as i32; + if last_alt - alt > 1 + (step + 3) / 4 + || sample.riverless_alt - sample.alt > 2.0 + { + break test_start.with_z(last_alt); + } else { + let water_dist = sample.water_dist.unwrap_or(16.0) as i32; - let mut start = start.with_z(land.get_alt_approx(start) as i32); - let mut end = end.with_z(land.get_alt_approx(end) as i32); - if start.z > end.z { - std::mem::swap(&mut start, &mut end); - } + test_start += step * dir; + if water_dist <= min_water_dist { + break test_start.with_z(alt); + } + + step = water_dist - min_water_dist; + + last_alt = alt; + } + } else { + break test_start.with_z(last_alt); + } + + /* + let alt = land.get_alt_approx(test_start + dir); + let is_water = land.get_chunk_wpos(test_start + dir * 5).map_or(false, |chunk| chunk.river.river_kind.is_some()); + if is_water || last_alt - alt as i32 > 1 || start.z - alt as i32 > 5 { + break test_start.with_z(last_alt); + } + last_alt = alt as i32; + + test_start += dir; + */ + } + }; + + let test_start = find_edge(start, end); + + let test_end = find_edge(end, start); + + let (start, end) = if test_start.z < test_end.z { + (test_start, test_end) + } else { + (test_end, test_start) + }; + + let center = (start.xy() + end.xy()) / 2; let center = center.with_z(land.get_alt_approx(center) as i32); + let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); + Self { start, end, center, dir: Dir::from_vector(end.xy() - start.xy()), - kind: if end.z - start.z > 10 { + kind: if end.z - start.z > 14 { BridgeKind::Tower(match rng.gen_range(0..=2) { 0 => RoofKind::Crenelated, _ => RoofKind::Hipped, }) + } else if len < 40 { + BridgeKind::Short } else { BridgeKind::Flat }, - width, + width: 8, } } } @@ -337,8 +490,9 @@ impl Bridge { impl Structure for Bridge { fn render(&self, _site: &Site, _land: &Land, painter: &Painter) { match &self.kind { - BridgeKind::Flat => render_flat(&self, painter), - BridgeKind::Tower(roof) => render_tower(&self, painter, roof), + BridgeKind::Flat => render_flat(self, painter), + BridgeKind::Tower(roof) => render_tower(self, painter, roof), + BridgeKind::Short => render_short(self, painter), } } } diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 4b0e3ed3df..a296936eb8 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -13,7 +13,6 @@ pub enum Dir { } impl Dir { - pub const ALL: [Dir; 4] = [Dir::X, Dir::Y, Dir::NegX, Dir::NegY]; pub fn choose(rng: &mut impl Rng) -> Dir { From 31b56b03b53fdfa2f97a3bc808a3edc1f92f6349 Mon Sep 17 00:00:00 2001 From: IsseW Date: Tue, 27 Sep 2022 15:33:14 +0200 Subject: [PATCH 04/14] desert skin --- world/src/site2/plot/bridge.rs | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index e6bbbc80be..4d87f1dc84 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -1,6 +1,6 @@ use super::*; use crate::{site2::gen::PrimitiveTransform, Land}; -use common::terrain::{Block, BlockKind}; +use common::terrain::{BiomeKind, Block, BlockKind}; use rand::prelude::*; use vek::*; @@ -26,8 +26,16 @@ fn aabb(min: Vec3, max: Vec3) -> Aabb { } fn render_short(bridge: &Bridge, painter: &Painter) { - let rock = Fill::Brick(BlockKind::Rock, Rgb::gray(70), 25); - let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(140))); + let (bridge_fill, edge_fill) = match bridge.biome { + BiomeKind::Desert | BiomeKind::Savannah => ( + Fill::Block(Block::new(BlockKind::Rock, Rgb::new(212, 191, 142))), + Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(190))), + ), + _ => ( + Fill::Brick(BlockKind::Rock, Rgb::gray(70), 25), + Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130))), + ), + }; let bridge_width = 3; @@ -90,15 +98,19 @@ fn render_short(bridge: &Bridge, painter: &Painter) { let b = bridge_prim(bridge_width); let t = 4; - b.union(painter.aabb(aabb( - (bridge.start.xy() - side - forward * (top - bridge.start.z - ramp_in + outset)).with_z(bridge.start.z - t), - (bridge.end.xy() + side + forward * (top - bridge.end.z - ramp_in + outset)).with_z(bridge.start.z), - ))) + b.union( + painter.aabb(aabb( + (bridge.start.xy() - side - forward * (top - bridge.start.z - ramp_in + outset)) + .with_z(bridge.start.z - t), + (bridge.end.xy() + side + forward * (top - bridge.end.z - ramp_in + outset)) + .with_z(bridge.start.z), + )), + ) .translate(Vec3::new(0, 0, t)) .without(b) .clear(); - b.without(remove).fill(rock.clone()); + b.without(remove).fill(bridge_fill); let prim = bridge_prim(bridge_width + 1); @@ -108,7 +120,7 @@ fn render_short(bridge: &Bridge, painter: &Painter) { bridge.start - side - forward * outset, (bridge.end.xy() + side + forward * outset).with_z(top + 1), ))) - .fill(light_rock); + .fill(edge_fill); } fn render_flat(bridge: &Bridge, painter: &Painter) { @@ -191,7 +203,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { max: tower_center + tower_size, }; - let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs(); + let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs() - 1; painter .aabb(aabb( @@ -391,6 +403,7 @@ pub struct Bridge { pub(crate) dir: Dir, center: Vec3, kind: BridgeKind, + biome: BiomeKind, } impl Bridge { @@ -483,6 +496,9 @@ impl Bridge { BridgeKind::Flat }, width: 8, + biome: land + .get_chunk_wpos(center.xy()) + .map_or(BiomeKind::Void, |chunk| chunk.get_biome()), } } } From 91ed3c6a84bc4692b02824cf6dfaf5ab5509c6a7 Mon Sep 17 00:00:00 2001 From: IsseW Date: Thu, 29 Sep 2022 17:35:48 +0200 Subject: [PATCH 05/14] draw on map --- world/examples/water.rs | 1 + world/src/civ/mod.rs | 2 + world/src/sim/map.rs | 20 +++- world/src/sim/mod.rs | 4 +- world/src/site2/mod.rs | 34 ------ world/src/site2/plot/bridge.rs | 191 ++++++++++++++++++++++++++++++++- world/src/site2/util/mod.rs | 16 ++- 7 files changed, 224 insertions(+), 44 deletions(-) diff --git a/world/examples/water.rs b/world/examples/water.rs index dfbab57437..1a023d2ecb 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -81,6 +81,7 @@ fn main() { sample_pos( config, sampler, + index, samples, uniform_idx_as_vec2(map_size_lg, posi), ) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 782885cac0..6f926830a8 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1626,6 +1626,8 @@ impl Site { } pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } + + pub fn is_bridge(&self) -> bool { matches!(self.kind, SiteKind::Bridge(_, _)) } } #[derive(PartialEq, Eq, Debug, Clone)] diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index a2b340f55d..d6184bc156 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -1,7 +1,7 @@ use crate::{ column::ColumnSample, sim::{RiverKind, WorldSim}, - CONFIG, + CONFIG, IndexRef, site::SiteKind, }; use common::{ terrain::{ @@ -71,6 +71,7 @@ pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2) -> f pub fn sample_pos( config: &MapConfig, sampler: &WorldSim, + index: IndexRef, samples: Option<&[Option]>, pos: Vec2, ) -> MapSample { @@ -102,6 +103,7 @@ pub fn sample_pos( river_kind, spline_derivative, is_path, + is_bridge, ) = sampler .get(pos) .map(|sample| { @@ -116,6 +118,17 @@ pub fn sample_pos( sample.river.river_kind, sample.river.spline_derivative, sample.path.0.is_way(), + sample.sites.iter().any(|site| match &index.sites.get(*site).kind { + SiteKind::Bridge(bridge) => if let Some(plot) = bridge.wpos_tile(TerrainChunkSize::center_wpos(pos)).plot { + match bridge.plot(plot).kind { + crate::site2::PlotKind::Bridge(_) => true, + _ => false + } + } else { + false + }, + _ => false, + }), ) }) .unwrap_or(( @@ -129,6 +142,7 @@ pub fn sample_pos( None, Vec2::zero(), false, + false, )); let humidity = humidity.clamp(0.0, 1.0); @@ -246,7 +260,9 @@ pub fn sample_pos( } }; // TODO: Make principled. - let rgb = if is_path { + let rgb = if is_bridge { + Rgb::new(0x80, 0x80, 0x80) + } else if is_path { Rgb::new(0x37, 0x29, 0x23) } else { rgb diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index aa0f981fe0..ea27af8096 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -28,7 +28,7 @@ pub(crate) use self::{ use crate::{ all::{Environment, ForestKind, TreeAttr}, block::BlockGen, - civ::{Place, PointOfInterest}, + civ::{Place, PointOfInterest, self}, column::ColumnGen, layer::spot::Spot, site::Site, @@ -1657,7 +1657,7 @@ impl WorldSim { map_config.is_shaded = false; map_config.generate( - |pos| sample_pos(&map_config, self, Some(&samples_data), pos), + |pos| sample_pos(&map_config, self, index, Some(&samples_data), pos), |pos| sample_wpos(&map_config, self, pos), |pos, (r, g, b, _a)| { // We currently ignore alpha and replace it with the height at pos, scaled to diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 44f310db54..274388c59c 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1058,40 +1058,6 @@ impl Site { hard_alt: None, }); - - let size = (1.5 + rng.gen::().powf(5.0) * 1.0).round() as u32; - if let Some((aabr, door_tile, door_dir)) = attempt(32, || { - site.find_roadside_aabr( - &mut rng, - 4..(size + 1).pow(2), - Extent2::broadcast(size), - ) - }) { - let house = plot::House::generate( - land, - &mut reseed(&mut rng), - &site, - door_tile, - door_dir, - aabr, - ); - let house_alt = house.alt; - let plot = site.create_plot(Plot { - kind: PlotKind::House(house), - root_tile: aabr.center(), - tiles: aabr_tiles(aabr).collect(), - seed: rng.gen(), - }); - - site.blit_aabr(aabr, Tile { - kind: TileKind::Building, - plot: Some(plot), - hard_alt: Some(house_alt), - }); - } else { - site.make_plaza(land, &mut rng); - } - site } diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 4d87f1dc84..4aa6c74857 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -11,10 +11,33 @@ enum RoofKind { Hipped, } +struct HeightenedViaduct { + slope_inv: i32, + bridge_start_offset: i32, + vault_spacing: i32, + vault_size: (i32, i32), + side_vault_size: (i32, i32), + holes: bool, +} + +impl HeightenedViaduct { + fn random(rng: &mut impl Rng) -> Self { + Self { + slope_inv: rng.gen_range(6..=8), + bridge_start_offset: rng.gen_range(5..=12), + vault_spacing: rng.gen_range(3..=4), + vault_size: *[(3, 16), (1, 4), (1, 4), (1, 4), (5, 32), (5, 32)].choose(rng).unwrap(), + side_vault_size: *[(4, 5), (7, 10), (7, 10), (13, 20)].choose(rng).unwrap(), + holes: rng.gen_bool(0.5), + } + } +} + enum BridgeKind { Flat, Tower(RoofKind), Short, + HeightenedViaduct(HeightenedViaduct), } fn aabb(min: Vec3, max: Vec3) -> Aabb { @@ -180,6 +203,169 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { .fill(rock); } +fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &HeightenedViaduct) { + let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130))); + let orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); + let forward = bridge.dir.to_vec2(); + + let slope_inv = data.slope_inv; + + let len = (bridge.start.xy() - bridge.end.xy()) + .map(|e| e.abs()) + .reduce_max(); + + let bridge_start_z = bridge.end.z + data.bridge_start_offset; + let bridge_top = bridge_start_z + len / slope_inv / 2; + + // painter.ramp(aabb( + // bridge.start - side, + // (bridge.start.xy() + side + forward * (bridge_start_z - + // bridge.start.z)).with_z(bridge_start_z), ), bridge_start_z - + // bridge.start.z, bridge.dir).fill(rock); + + let bridge_width = bridge.width; + let side = orthogonal * bridge_width; + + let aabr = Aabr { + min: bridge.start.xy() - side, + max: bridge.end.xy() + side, + } + .made_valid(); + + let [_start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z); + let [_end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z); + let under = bridge.center.z - 15; + + let bridge_prim = |bridge_width: i32| { + let side = orthogonal * bridge_width; + + let aabr = Aabr { + min: bridge.start.xy() - side, + max: bridge.end.xy() + side, + } + .made_valid(); + + let [start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z); + let [end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z); + let [bridge_start, bridge_end] = bridge + .dir + .split_aabr(bridge_aabr, bridge.dir.select(bridge_aabr.size()) / 2); + + let ramp_in_aabr = |aabr: Aabr, dir: Dir, zmin, zmax| { + let inset = dir.select(aabr.size()); + painter.ramp( + aabb(aabr.min.with_z(zmin), aabr.max.with_z(zmax)), + inset, + dir, + ) + }; + + ramp_in_aabr(start_aabr, bridge.dir, bridge.start.z, bridge_start_z) + .union( + ramp_in_aabr(end_aabr, -bridge.dir, bridge.end.z, bridge_start_z) + .union(ramp_in_aabr( + bridge_start, + bridge.dir, + bridge_start_z + 1, + bridge_top, + )) + .union(ramp_in_aabr( + bridge_end, + -bridge.dir, + bridge_start_z + 1, + bridge_top, + )), + ) + .union( + painter + .aabb(aabb( + start_aabr.min.with_z(under), + start_aabr.max.with_z(bridge.start.z - 1), + )) + .union(painter.aabb(aabb( + end_aabr.min.with_z(under), + end_aabr.max.with_z(bridge.end.z - 1), + ))), + ) + .union(painter.aabb(aabb( + bridge_aabr.min.with_z(under), + bridge_aabr.max.with_z(bridge_start_z), + ))) + }; + + let b = bridge_prim(bridge_width - 1); + let b = b.without(b.translate(-Vec3::unit_z())); + + let c = bridge_aabr.center(); + let len = bridge.dir.select(bridge_aabr.size()); + let vault_size = data.vault_size.0 * len / data.vault_size.1; + let side_vault = data.side_vault_size.0 * vault_size / data.side_vault_size.1; + let vertical = 5; + let spacing = data.vault_spacing; + let vault_top = bridge_top - vertical; + let side_vault_top = vault_top - (vault_size + spacing + 1 + side_vault) / slope_inv; + let side_vault_offset = vault_size + spacing + 1; + + let mut remove = painter.vault( + aabb( + (c - side - forward * vault_size).with_z(under), + (c + side + forward * vault_size).with_z(vault_top), + ), + orth_dir, + ); + + if side_vault * 2 + side_vault_offset < len / 2 + 5 { + remove = remove.union( + painter + .vault( + aabb( + (c - side + forward * side_vault_offset).with_z(under), + (c + side + forward * (side_vault * 2 + side_vault_offset)) + .with_z(side_vault_top), + ), + orth_dir, + ) + .union( + painter.vault( + aabb( + (c - side - forward * side_vault_offset).with_z(under), + (c + side - forward * (side_vault * 2 + side_vault_offset)) + .with_z(side_vault_top), + ), + orth_dir, + ), + ), + ); + + if data.holes { + remove = remove.union( + painter.vault(aabb( + (c - side + forward * (vault_size + 1)).with_z(side_vault_top - 4), + (c + side + forward * (vault_size + spacing)).with_z(side_vault_top + 2), + ), orth_dir).union( + painter.vault(aabb( + (c - side - forward * (vault_size + 1)).with_z(side_vault_top - 4), + (c + side - forward * (vault_size + spacing)).with_z(side_vault_top + 2), + ), orth_dir) + ) + ); + } + } + + bridge_prim(bridge_width) + .without(b) + .without(remove) + .fill(rock.clone()); + b.translate(-Vec3::unit_z()).fill(light_rock.clone()); + + b.translate(Vec3::unit_z() * 5) + .without(b.translate(-Vec3::unit_z())) + .clear(); +} + fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(40, 28, 20))); @@ -485,13 +671,15 @@ impl Bridge { end, center, dir: Dir::from_vector(end.xy() - start.xy()), - kind: if end.z - start.z > 14 { + kind: if end.z - start.z > 17 { BridgeKind::Tower(match rng.gen_range(0..=2) { 0 => RoofKind::Crenelated, _ => RoofKind::Hipped, }) } else if len < 40 { BridgeKind::Short + } else if end.z - start.z < 5 && start.z - center.z < 16 { + BridgeKind::HeightenedViaduct(HeightenedViaduct::random(rng)) } else { BridgeKind::Flat }, @@ -509,6 +697,7 @@ impl Structure for Bridge { BridgeKind::Flat => render_flat(self, painter), BridgeKind::Tower(roof) => render_tower(self, painter, roof), BridgeKind::Short => render_short(self, painter), + BridgeKind::HeightenedViaduct(data) => render_heightened_viaduct(self, painter, data), } } } diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index a296936eb8..89b64de88f 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -1,5 +1,7 @@ pub mod gradient; +use std::ops::{Add, Sub}; + use rand::Rng; use vek::*; @@ -168,7 +170,8 @@ impl Dir { pub fn is_y(self) -> bool { matches!(self, Dir::Y | Dir::NegY) } /// Returns the component that the direction is parallell to - pub fn select(self, vec: Vec2) -> i32 { + pub fn select(self, vec: impl Into>) -> i32 { + let vec = vec.into(); match self { Dir::X | Dir::NegX => vec.x, Dir::Y | Dir::NegY => vec.y, @@ -177,7 +180,9 @@ impl Dir { /// Select one component the direction is parallel to from vec and select /// the other component from other - pub fn select_with(self, vec: Vec2, other: Vec2) -> Vec2 { + pub fn select_with(self, vec: impl Into>, other: impl Into>) -> Vec2 { + let vec = vec.into(); + let other = other.into(); match self { Dir::X | Dir::NegX => Vec2::new(vec.x, other.y), Dir::Y | Dir::NegY => Vec2::new(other.x, vec.y), @@ -185,7 +190,7 @@ impl Dir { } /// Returns the side of an aabr that the direction is pointing to - pub fn select_aabr(self, aabr: Aabr) -> i32 { + pub fn select_aabr(self, aabr: Aabr) -> T { match self { Dir::X => aabr.max.x, Dir::NegX => aabr.min.x, @@ -196,7 +201,8 @@ impl Dir { /// Select one component from the side the direction is pointing to from /// aabr and select the other component from other - pub fn select_aabr_with(self, aabr: Aabr, other: Vec2) -> Vec2 { + pub fn select_aabr_with(self, aabr: Aabr, other: impl Into>) -> Vec2 { + let other = other.into(); match self { Dir::X => Vec2::new(aabr.max.x, other.y), Dir::NegX => Vec2::new(aabr.min.x, other.y), @@ -215,7 +221,7 @@ impl Dir { } } - pub fn split_aabr(self, aabr: Aabr, offset: i32) -> [Aabr; 2] { + pub fn split_aabr(self, aabr: Aabr, offset: T) -> [Aabr; 2] where T: Copy + PartialOrd + Add + Sub { match self { Dir::X => aabr.split_at_x(aabr.min.x + offset), Dir::Y => aabr.split_at_y(aabr.min.y + offset), From df8db713ea13d3e7d459fdee7cec86d2cdc97945 Mon Sep 17 00:00:00 2001 From: IsseW Date: Sun, 9 Oct 2022 21:23:03 +0200 Subject: [PATCH 06/14] lights --- world/src/sim/mod.rs | 2 +- world/src/site2/plot/bridge.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index ea27af8096..92d1983eca 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -28,7 +28,7 @@ pub(crate) use self::{ use crate::{ all::{Environment, ForestKind, TreeAttr}, block::BlockGen, - civ::{Place, PointOfInterest, self}, + civ::{Place, PointOfInterest}, column::ColumnGen, layer::spot::Spot, site::Site, diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 4aa6c74857..e6963b63b5 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -296,8 +296,8 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten ))) }; - let b = bridge_prim(bridge_width - 1); - let b = b.without(b.translate(-Vec3::unit_z())); + let br = bridge_prim(bridge_width - 1); + let b = br.without(br.translate(-Vec3::unit_z())); let c = bridge_aabr.center(); let len = bridge.dir.select(bridge_aabr.size()); @@ -356,14 +356,35 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten } bridge_prim(bridge_width) - .without(b) .without(remove) .fill(rock.clone()); b.translate(-Vec3::unit_z()).fill(light_rock.clone()); - b.translate(Vec3::unit_z() * 5) - .without(b.translate(-Vec3::unit_z())) + br.translate(Vec3::unit_z() * 5) + .without(br.translate(-Vec3::unit_z())) .clear(); + + + let place_lights = |center: Vec3| { + painter.sprite(orth_dir.select_aabr_with(bridge_aabr, center.xy()).with_z(center.z), SpriteKind::FireBowlGround); + painter.sprite((-orth_dir).select_aabr_with(bridge_aabr, center.xy()).with_z(center.z), SpriteKind::FireBowlGround); + }; + + place_lights(bridge_aabr.center().with_z(bridge_top + 1)); + + let light_spacing = 1; + let num_lights = (len - 1) / 2 / light_spacing; + + let place_lights = |i: i32| { + let offset = i * light_spacing; + let z = bridge_start_z + 1 + (offset + if len / 2 % 2 == 0 { 4 } else { 3 }) / (slope_inv - 1); + + place_lights((bridge.dir.select_aabr_with(bridge_aabr, bridge_aabr.center()) - forward * offset).with_z(z)); + place_lights(((-bridge.dir).select_aabr_with(bridge_aabr, bridge_aabr.center()) + forward * offset).with_z(z)); + }; + for i in 0..num_lights { + place_lights(i); + } } fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { From 6d2b39c254d8fc4ebdf51b4cc78af7719c0ba1f4 Mon Sep 17 00:00:00 2001 From: IsseW Date: Fri, 28 Oct 2022 19:13:29 +0200 Subject: [PATCH 07/14] simple hang bridge --- world/src/site2/gen.rs | 50 ++++-- world/src/site2/mod.rs | 2 +- world/src/site2/plot/bridge.rs | 271 ++++++++++++++++++++++++++------- 3 files changed, 253 insertions(+), 70 deletions(-) diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index b7d8bba747..c6f58687c9 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -15,7 +15,7 @@ use common::{ vol::ReadVol, }; use num::cast::AsPrimitive; -use std::{cell::RefCell, sync::Arc}; +use std::{cell::RefCell, sync::Arc, ops::RangeBounds}; use vek::*; #[allow(dead_code)] @@ -289,13 +289,10 @@ impl Fill { - ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32 }, Primitive::Cylinder(aabb) => { + let fpos = pos.as_::().xy() - aabb.as_::().center().xy(); + let size = Vec3::from(aabb.size().as_::()).xy(); (aabb.min.z..aabb.max.z).contains(&pos.z) - && (pos - .xy() - .as_() - .distance_squared(aabb.as_().center().xy() - 0.5) - as f32) - < (aabb.size().w.min(aabb.size().h) as f32 / 2.0).powi(2) + && (2.0 * fpos / size).magnitude_squared() <= 1.0 }, Primitive::Cone(aabb) => { (aabb.min.z..aabb.max.z).contains(&pos.z) @@ -390,9 +387,9 @@ impl Fill { }, Primitive::Scale(prim, vec) => { let center = - Self::get_bounds(tree, *prim).center().as_::() - Vec3::broadcast(0.5); + Self::get_bounds(tree, *prim).as_::().center(); let fpos = pos.as_::(); - let spos = (center + ((center - fpos) / vec)) + let spos = (center + ((fpos - center) / vec)) .map(|x| x.round()) .as_::(); Self::contains_at(tree, *prim, spos) @@ -782,6 +779,20 @@ impl Painter { self.prim(Primitive::Cylinder(aabb.made_valid())) } + + /// Returns a `PrimitiveRef` of the largest horizontal cylinder that fits in the + /// provided Aabb. + pub fn horizontal_cylinder(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { + let aabr = Aabr::from(aabb); + let length = dir.select(aabr.size()); + let height = aabb.max.z - aabb.min.z; + let aabb = Aabb { + min: (aabr.min - dir.abs().to_vec2() * height).with_z(aabb.min.z), + max: (dir.abs().select_with(aabr.min, aabr.max)).with_z(aabb.min.z + length), + }; + self.cylinder(aabb).rotate_about((-dir.abs()).from_z_mat3(), aabr.min.with_z(aabb.min.z)) + } + /// Returns a `PrimitiveRef` of a cylinder using a radius check where a /// radius and origin are parameters instead of a bounding box. pub fn cylinder_with_radius( @@ -1169,6 +1180,21 @@ impl Painter { prim } + pub fn column(&self, point: Vec2, range: impl RangeBounds) -> PrimitiveRef { + self.aabb(Aabb { + min: point.with_z(match range.start_bound() { + std::ops::Bound::Included(n) => *n, + std::ops::Bound::Excluded(n) => n + 1, + std::ops::Bound::Unbounded => i32::MIN, + }), + max: (point + 1).with_z(match range.end_bound() { + std::ops::Bound::Included(n) => n + 1, + std::ops::Bound::Excluded(n) => *n, + std::ops::Bound::Unbounded => i32::MAX, + }), + }) + } + /// The area that the canvas is currently rendering. pub fn render_aabr(&self) -> Aabr { self.render_area } @@ -1250,7 +1276,7 @@ pub trait PrimitiveTransform { /// Scales the primitive along each axis by the x, y, and z components of /// the `scale` vector respectively. #[must_use] - fn scale(self, scale: Vec3) -> Self; + fn scale(self, scale: Vec3>) -> Self; /// Returns a `PrimitiveRef` of the primitive in addition to the same /// primitive translated by `offset` and repeated `count` times, each time /// translated by an additional offset. @@ -1267,7 +1293,7 @@ impl<'a> PrimitiveTransform for PrimitiveRef<'a> { self.painter.prim(Primitive::rotate_about(self, rot, point)) } - fn scale(self, scale: Vec3) -> Self { self.painter.prim(Primitive::scale(self, scale)) } + fn scale(self, scale: Vec3>) -> Self { self.painter.prim(Primitive::scale(self, scale.as_())) } fn repeat(self, offset: Vec3, count: u32) -> Self { self.painter.prim(Primitive::repeat(self, offset, count)) @@ -1289,7 +1315,7 @@ impl<'a, const N: usize> PrimitiveTransform for [PrimitiveRef<'a>; N] { self } - fn scale(mut self, scale: Vec3) -> Self { + fn scale(mut self, scale: Vec3>) -> Self { for prim in &mut self { *prim = prim.scale(scale); } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 274388c59c..7dee9dca37 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1024,7 +1024,7 @@ impl Site { let start_tile = site.wpos_tile_pos(bridge.start.xy()); let end_tile = site.wpos_tile_pos(bridge.end.xy()); - let width = (bridge.width + TILE_SIZE as i32 / 2) / TILE_SIZE as i32; + let width = (bridge.width() + TILE_SIZE as i32 / 2) / TILE_SIZE as i32; let aabr = Aabr { min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width, max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width, diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index e6963b63b5..3034375057 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -1,6 +1,10 @@ use super::*; use crate::{site2::gen::PrimitiveTransform, Land}; -use common::terrain::{BiomeKind, Block, BlockKind}; +use common::{ + generation::EntityInfo, + terrain::{BiomeKind, Block, BlockKind}, +}; +use num::integer::Roots; use rand::prelude::*; use vek::*; @@ -26,7 +30,9 @@ impl HeightenedViaduct { slope_inv: rng.gen_range(6..=8), bridge_start_offset: rng.gen_range(5..=12), vault_spacing: rng.gen_range(3..=4), - vault_size: *[(3, 16), (1, 4), (1, 4), (1, 4), (5, 32), (5, 32)].choose(rng).unwrap(), + vault_size: *[(3, 16), (1, 4), (1, 4), (1, 4), (5, 32), (5, 32)] + .choose(rng) + .unwrap(), side_vault_size: *[(4, 5), (7, 10), (7, 10), (13, 20)].choose(rng).unwrap(), holes: rng.gen_bool(0.5), } @@ -38,6 +44,42 @@ enum BridgeKind { Tower(RoofKind), Short, HeightenedViaduct(HeightenedViaduct), + HangBridge, +} + +impl BridgeKind { + fn random( + rng: &mut impl Rng, + start: Vec3, + center: Vec3, + end: Vec3, + ) -> BridgeKind { + let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); + (0..=3) + .filter_map(|bridge| match bridge { + 0 if end.z - start.z > 17 => Some(BridgeKind::Tower(match rng.gen_range(0..=2) { + 0 => RoofKind::Crenelated, + _ => RoofKind::Hipped, + })), + 1 if len < 40 => Some(BridgeKind::Short), + 2 if end.z - start.z < 5 && start.z - center.z < 16 => Some( + BridgeKind::HeightenedViaduct(HeightenedViaduct::random(rng)), + ), + 3 if end.z - start.z < 9 && start.z - center.z > 10 => Some(BridgeKind::HangBridge), + _ => None, + }) + .collect::>() + .into_iter() + .choose(rng) + .unwrap_or(BridgeKind::Flat) + } + + fn width(&self) -> i32 { + match self { + BridgeKind::HangBridge => 2, + _ => 8, + } + } } fn aabb(min: Vec3, max: Vec3) -> Aabb { @@ -162,7 +204,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { let size = tweak!(8); let hole = painter .cylinder(aabb( - bridge.center.with_z(bridge.end.z - 3 - bridge.width * 2) - Vec2::broadcast(size), + bridge.center.with_z(bridge.end.z - 3 - bridge.width() * 2) - Vec2::broadcast(size), bridge.center.with_z(bridge.end.z + 2) + Vec2::broadcast(size), )) .rotate_about( @@ -170,7 +212,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { bridge .center .as_() - .with_z(bridge.end.z as f32 - 1.5 - bridge.width as f32), + .with_z(bridge.end.z as f32 - 1.5 - bridge.width() as f32), ) .scale( bridge.dir.abs().to_vec3().as_() @@ -182,9 +224,9 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { .ramp( aabb( bridge.start.with_z(bridge.start.z - inset) - - orthogonal * bridge.width + - orthogonal * bridge.width() - forward * inset, - bridge.start.with_z(bridge.end.z) + orthogonal * bridge.width + forward * dz, + bridge.start.with_z(bridge.end.z) + orthogonal * bridge.width() + forward * dz, ), dz + inset + 1, bridge.dir, @@ -192,10 +234,10 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { .union( painter .aabb(aabb( - bridge.start.with_z(bridge.end.z - 3 - size) - orthogonal * bridge.width + bridge.start.with_z(bridge.end.z - 3 - size) - orthogonal * bridge.width() + forward * dz, bridge.start.with_z(bridge.end.z) - + orthogonal * bridge.width + + orthogonal * bridge.width() + forward * forward * (bridge.end.xy() - bridge.start.xy()), )) .without(hole), @@ -226,7 +268,7 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten // bridge.start.z)).with_z(bridge_start_z), ), bridge_start_z - // bridge.start.z, bridge.dir).fill(rock); - let bridge_width = bridge.width; + let bridge_width = bridge.width(); let side = orthogonal * bridge_width; let aabr = Aabr { @@ -317,6 +359,14 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten orth_dir, ); + let mut rng = thread_rng(); + if rng.gen_bool(0.1) { + painter.spawn( + EntityInfo::at(c.with_z(bridge.center.z).as_()) + .with_asset_expect("common.entity.wild.aggressive.swamp_troll", &mut rng), + ); + } + if side_vault * 2 + side_vault_offset < len / 2 + 5 { remove = remove.union( painter @@ -339,35 +389,52 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten ), ), ); - + if data.holes { remove = remove.union( - painter.vault(aabb( - (c - side + forward * (vault_size + 1)).with_z(side_vault_top - 4), - (c + side + forward * (vault_size + spacing)).with_z(side_vault_top + 2), - ), orth_dir).union( - painter.vault(aabb( - (c - side - forward * (vault_size + 1)).with_z(side_vault_top - 4), - (c + side - forward * (vault_size + spacing)).with_z(side_vault_top + 2), - ), orth_dir) - ) + painter + .vault( + aabb( + (c - side + forward * (vault_size + 1)).with_z(side_vault_top - 4), + (c + side + forward * (vault_size + spacing)) + .with_z(side_vault_top + 2), + ), + orth_dir, + ) + .union( + painter.vault( + aabb( + (c - side - forward * (vault_size + 1)).with_z(side_vault_top - 4), + (c + side - forward * (vault_size + spacing)) + .with_z(side_vault_top + 2), + ), + orth_dir, + ), + ), ); } } - bridge_prim(bridge_width) - .without(remove) - .fill(rock.clone()); + bridge_prim(bridge_width).without(remove).fill(rock.clone()); b.translate(-Vec3::unit_z()).fill(light_rock.clone()); br.translate(Vec3::unit_z() * 5) .without(br.translate(-Vec3::unit_z())) .clear(); - let place_lights = |center: Vec3| { - painter.sprite(orth_dir.select_aabr_with(bridge_aabr, center.xy()).with_z(center.z), SpriteKind::FireBowlGround); - painter.sprite((-orth_dir).select_aabr_with(bridge_aabr, center.xy()).with_z(center.z), SpriteKind::FireBowlGround); + painter.sprite( + orth_dir + .select_aabr_with(bridge_aabr, center.xy()) + .with_z(center.z), + SpriteKind::FireBowlGround, + ); + painter.sprite( + (-orth_dir) + .select_aabr_with(bridge_aabr, center.xy()) + .with_z(center.z), + SpriteKind::FireBowlGround, + ); }; place_lights(bridge_aabr.center().with_z(bridge_top + 1)); @@ -377,10 +444,20 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten let place_lights = |i: i32| { let offset = i * light_spacing; - let z = bridge_start_z + 1 + (offset + if len / 2 % 2 == 0 { 4 } else { 3 }) / (slope_inv - 1); - - place_lights((bridge.dir.select_aabr_with(bridge_aabr, bridge_aabr.center()) - forward * offset).with_z(z)); - place_lights(((-bridge.dir).select_aabr_with(bridge_aabr, bridge_aabr.center()) + forward * offset).with_z(z)); + let z = + bridge_start_z + 1 + (offset + if len / 2 % 2 == 0 { 4 } else { 3 }) / (slope_inv - 1); + + place_lights( + (bridge + .dir + .select_aabr_with(bridge_aabr, bridge_aabr.center()) + - forward * offset) + .with_z(z), + ); + place_lights( + ((-bridge.dir).select_aabr_with(bridge_aabr, bridge_aabr.center()) + forward * offset) + .with_z(z), + ); }; for i in 0..num_lights { place_lights(i); @@ -603,10 +680,112 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { } } +fn render_hang(bridge: &Bridge, painter: &Painter) { + let orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); + let forward = bridge.dir.to_vec2(); + + let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(133, 94, 66))); + + let bridge_width = bridge.width(); + let side = orthogonal * bridge_width; + + let aabr = Aabr { + min: bridge.start.xy() - side, + max: bridge.end.xy() + side, + } + .made_valid(); + + let top_offset = 4; + let top = bridge.end.z + top_offset; + + let [ramp_f, aabr] = bridge.dir.split_aabr(aabr, top - bridge.start.z + 1); + + painter + .aabb(aabb( + ramp_f.min.with_z(bridge.start.z - 10), + ramp_f.max.with_z(bridge.start.z), + )) + .fill(rock.clone()); + painter + .ramp( + aabb(ramp_f.min.with_z(bridge.start.z), ramp_f.max.with_z(top)), + top - bridge.start.z + 1, + bridge.dir, + ) + .fill(rock.clone()); + + let [ramp_b, aabr] = (-bridge.dir).split_aabr(aabr, top_offset + 1); + painter + .aabb(aabb( + ramp_b.min.with_z(bridge.end.z - 10), + ramp_b.max.with_z(bridge.end.z), + )) + .fill(rock.clone()); + painter + .ramp( + aabb(ramp_b.min.with_z(bridge.end.z), ramp_b.max.with_z(top)), + top - bridge.end.z, + -bridge.dir, + ) + .fill(rock.clone()); + + let len = bridge.dir.select(aabr.size()); + + let h = 3 * len.sqrt() / 4; + + let x = len / 2; + + let xsqr = (x * x) as f32; + let hsqr = (h * h) as f32; + let w = ((xsqr + (xsqr * (4.0 * hsqr + xsqr)).sqrt()) / 2.0).sqrt().ceil() + 1.0; + + + let bottom = top - (h - (hsqr - hsqr * x as f32 / w).sqrt().ceil() as i32); + + let w = w as i32; + let c = aabr.center(); + + let cylinder = painter + .horizontal_cylinder( + aabb( + (c - forward * w - side).with_z(bottom), + (c + forward * w + side).with_z(bottom + h * 2), + ), + orth_dir, + ) + .intersect(painter.aabb(aabb(aabr.min.with_z(bottom), aabr.max.with_z(bottom + h * 2)))); + + cylinder.fill(wood.clone()); + + cylinder.translate(Vec3::unit_z()).clear(); + + let edges = cylinder + .without(cylinder.translate(Vec3::unit_z())) + .without(painter.aabb(aabb( + (c - forward * w - orthogonal * (bridge_width - 1)).with_z(bottom), + (c + forward * w + orthogonal * (bridge_width - 1)).with_z(bottom + h * 2), + ))); + + edges + .translate(Vec3::unit_z()) + .fill(Fill::Sprite(SpriteKind::Rope)); + + edges.translate(Vec3::unit_z() * 2).fill(wood.clone()); + + let column_height = 3; + let column_range = top..=top + column_height; + painter.column(bridge.dir.select_aabr_with(ramp_f, ramp_f.min), column_range.clone()).fill(rock.clone()); + painter.column(bridge.dir.select_aabr_with(ramp_f, ramp_f.max), column_range.clone()).fill(rock.clone()); + painter.column((-bridge.dir).select_aabr_with(ramp_b, ramp_b.min), column_range.clone()).fill(rock.clone()); + painter.column((-bridge.dir).select_aabr_with(ramp_b, ramp_b.max), column_range.clone()).fill(rock.clone()); +} + pub struct Bridge { pub(crate) start: Vec3, pub(crate) end: Vec3, - pub(crate) width: i32, pub(crate) dir: Dir, center: Vec3, kind: BridgeKind, @@ -658,17 +837,6 @@ impl Bridge { } else { break test_start.with_z(last_alt); } - - /* - let alt = land.get_alt_approx(test_start + dir); - let is_water = land.get_chunk_wpos(test_start + dir * 5).map_or(false, |chunk| chunk.river.river_kind.is_some()); - if is_water || last_alt - alt as i32 > 1 || start.z - alt as i32 > 5 { - break test_start.with_z(last_alt); - } - last_alt = alt as i32; - - test_start += dir; - */ } }; @@ -684,32 +852,20 @@ impl Bridge { let center = (start.xy() + end.xy()) / 2; let center = center.with_z(land.get_alt_approx(center) as i32); - - let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); - + let bridge = BridgeKind::random(rng, start, center, end); Self { start, end, center, dir: Dir::from_vector(end.xy() - start.xy()), - kind: if end.z - start.z > 17 { - BridgeKind::Tower(match rng.gen_range(0..=2) { - 0 => RoofKind::Crenelated, - _ => RoofKind::Hipped, - }) - } else if len < 40 { - BridgeKind::Short - } else if end.z - start.z < 5 && start.z - center.z < 16 { - BridgeKind::HeightenedViaduct(HeightenedViaduct::random(rng)) - } else { - BridgeKind::Flat - }, - width: 8, + kind: bridge, biome: land .get_chunk_wpos(center.xy()) .map_or(BiomeKind::Void, |chunk| chunk.get_biome()), } } + + pub fn width(&self) -> i32 { self.kind.width() } } impl Structure for Bridge { @@ -719,6 +875,7 @@ impl Structure for Bridge { BridgeKind::Tower(roof) => render_tower(self, painter, roof), BridgeKind::Short => render_short(self, painter), BridgeKind::HeightenedViaduct(data) => render_heightened_viaduct(self, painter, data), + BridgeKind::HangBridge => render_hang(self, painter), } } } From 179c6dab8fc43a439e8f82bc83c21b3fa2b55b60 Mon Sep 17 00:00:00 2001 From: IsseW Date: Sat, 29 Oct 2022 18:13:47 +0200 Subject: [PATCH 08/14] better flat --- world/src/civ/mod.rs | 22 ++-- world/src/sim/map.rs | 29 +++-- world/src/site2/gen.rs | 60 ++++----- world/src/site2/mod.rs | 3 +- world/src/site2/plot/bridge.rs | 214 +++++++++++++++++++++------------ world/src/site2/util/mod.rs | 12 +- 6 files changed, 195 insertions(+), 145 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 6f926830a8..0fb7f2b0a8 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -189,10 +189,7 @@ impl Civs { SiteKind::GiantTree => (12i32, 8.0), SiteKind::Gnarling => (16i32, 10.0), SiteKind::Citadel => (16i32, 0.0), - SiteKind::Bridge(start, end) => { - let e = (end - start).map(|e| e.abs()).reduce_max(); - ((e + 1) / 2, ((e + 1) / 2) as f32 / 2.0) - }, + SiteKind::Bridge(_, _) => (0, 0.0), }; let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { @@ -1264,9 +1261,9 @@ fn walk_in_dir( 3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics }; Some((a + dir, 1.0 + hill_cost + water_cost + wild_cost)) - } else if dir.x == 0 || dir.y == 0 { + } else if NEIGHBORS.iter().all(|p| get_bridge(a + p).is_none()) && (dir.x == 0 || dir.y == 0) { (4..=5).find_map(|i| { - loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, i as f32 * 30.0)) + loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, 400.0 + (i - 4) as f32 * 10.0)) }) } else { None @@ -1276,13 +1273,10 @@ fn walk_in_dir( /// Return true if a position is suitable for walking on fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2) -> bool { if sim.get(loc).is_some() { - NEIGHBORS - .iter() - .find(|n| { - sim.get(loc + *n) - .map_or(false, |chunk| chunk.river.near_water()) - }) - .is_none() + !NEIGHBORS.iter().any(|n| { + sim.get(loc + *n) + .map_or(false, |chunk| chunk.river.near_water()) + }) } else { false } @@ -1626,7 +1620,7 @@ impl Site { } pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } - + pub fn is_bridge(&self) -> bool { matches!(self.kind, SiteKind::Bridge(_, _)) } } diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index d6184bc156..dbe7ff75de 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -1,7 +1,8 @@ use crate::{ column::ColumnSample, sim::{RiverKind, WorldSim}, - CONFIG, IndexRef, site::SiteKind, + site::SiteKind, + IndexRef, CONFIG, }; use common::{ terrain::{ @@ -118,17 +119,21 @@ pub fn sample_pos( sample.river.river_kind, sample.river.spline_derivative, sample.path.0.is_way(), - sample.sites.iter().any(|site| match &index.sites.get(*site).kind { - SiteKind::Bridge(bridge) => if let Some(plot) = bridge.wpos_tile(TerrainChunkSize::center_wpos(pos)).plot { - match bridge.plot(plot).kind { - crate::site2::PlotKind::Bridge(_) => true, - _ => false - } - } else { - false - }, - _ => false, - }), + sample + .sites + .iter() + .any(|site| match &index.sites.get(*site).kind { + SiteKind::Bridge(bridge) => { + if let Some(plot) = + bridge.wpos_tile(TerrainChunkSize::center_wpos(pos)).plot + { + matches!(bridge.plot(plot).kind, crate::site2::PlotKind::Bridge(_)) + } else { + false + } + }, + _ => false, + }), ) }) .unwrap_or(( diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index c6f58687c9..8b6aaae367 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -15,7 +15,7 @@ use common::{ vol::ReadVol, }; use num::cast::AsPrimitive; -use std::{cell::RefCell, sync::Arc, ops::RangeBounds}; +use std::{cell::RefCell, ops::RangeBounds, sync::Arc}; use vek::*; #[allow(dead_code)] @@ -386,8 +386,7 @@ impl Fill { Self::contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub)) }, Primitive::Scale(prim, vec) => { - let center = - Self::get_bounds(tree, *prim).as_::().center(); + let center = Self::get_bounds(tree, *prim).as_::().center(); let fpos = pos.as_::(); let spos = (center + ((fpos - center) / vec)) .map(|x| x.round()) @@ -779,9 +778,8 @@ impl Painter { self.prim(Primitive::Cylinder(aabb.made_valid())) } - - /// Returns a `PrimitiveRef` of the largest horizontal cylinder that fits in the - /// provided Aabb. + /// Returns a `PrimitiveRef` of the largest horizontal cylinder that fits in + /// the provided Aabb. pub fn horizontal_cylinder(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { let aabr = Aabr::from(aabb); let length = dir.select(aabr.size()); @@ -790,7 +788,8 @@ impl Painter { min: (aabr.min - dir.abs().to_vec2() * height).with_z(aabb.min.z), max: (dir.abs().select_with(aabr.min, aabr.max)).with_z(aabb.min.z + length), }; - self.cylinder(aabb).rotate_about((-dir.abs()).from_z_mat3(), aabr.min.with_z(aabb.min.z)) + self.cylinder(aabb) + .rotate_about((-dir.abs()).from_z_mat3(), aabr.min.with_z(aabb.min.z)) } /// Returns a `PrimitiveRef` of a cylinder using a radius check where a @@ -1072,40 +1071,21 @@ impl Painter { /// |_____|/ /// ``` pub fn vault(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { - let aabr = Aabr::from(aabb); - let a = dir.select_aabr(aabr); - let b = (-dir).select_aabr(aabr); - let dmin = a.min(b); - let dmax = a.max(b); - let length = dmax - dmin; + let h = dir.orthogonal().select(Vec3::from(aabb.size()).xy()); - let c = dir.rotated_cw().select_aabr(aabr); - let d = dir.rotated_ccw().select_aabr(aabr); - let omin = c.min(d); - let omax = c.max(d); - let diameter = omax - omin; - let radius = (diameter + 1) / 2; - let min = (a * dir.to_vec2() + c * dir.orthogonal().to_vec2()).with_z(aabb.max.z); + let mut prim = self.horizontal_cylinder(Aabb { + min: aabb.min.with_z(aabb.max.z - h), + max: aabb.max, + }, dir); - self.cylinder( - Aabb { - min, - max: (a * dir.to_vec2() - + diameter * (-dir).to_vec2() - + d * dir.orthogonal().to_vec2()) - .with_z(aabb.max.z + length), - } - .made_valid(), - ) - .rotate_about(dir.abs().from_z_mat3(), min) - .without(self.aabb(Aabb { - min: aabb.min.with_z(aabb.min.z - radius), - max: aabb.max.with_z(aabb.min.z), - })) - .union(self.aabb(Aabb { + if aabb.size().d < h { + prim = prim.intersect(self.aabb(aabb)); + } + + self.aabb(Aabb { min: aabb.min, - max: aabb.max.with_z(aabb.max.z - radius), - })) + max: aabb.max.with_z(aabb.max.z - h / 2), + }).union(prim) } /// Place aabbs around another aabb in a symmetric and distributed manner. @@ -1293,7 +1273,9 @@ impl<'a> PrimitiveTransform for PrimitiveRef<'a> { self.painter.prim(Primitive::rotate_about(self, rot, point)) } - fn scale(self, scale: Vec3>) -> Self { self.painter.prim(Primitive::scale(self, scale.as_())) } + fn scale(self, scale: Vec3>) -> Self { + self.painter.prim(Primitive::scale(self, scale.as_())) + } fn repeat(self, offset: Vec3, count: u32) -> Self { self.painter.prim(Primitive::repeat(self, offset, count)) diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 7dee9dca37..0ff0604a10 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1018,7 +1018,6 @@ impl Site { max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width, }; - let bridge = plot::Bridge::generate(land, index, &mut rng, &site, start_tile, end_tile); let start_tile = site.wpos_tile_pos(bridge.start.xy()); @@ -1044,7 +1043,7 @@ impl Site { (-bridge.dir).select_aabr_with(start_aabr, aabr.center()), 2, ); - + let plot = site.create_plot(Plot { kind: PlotKind::Bridge(bridge), root_tile: start_tile, diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 3034375057..0757390d2e 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -25,16 +25,20 @@ struct HeightenedViaduct { } impl HeightenedViaduct { - fn random(rng: &mut impl Rng) -> Self { + fn random(rng: &mut impl Rng, height: i32) -> Self { + let vault_spacing = *[3, 4, 5, 7].choose(rng).unwrap(); Self { slope_inv: rng.gen_range(6..=8), - bridge_start_offset: rng.gen_range(5..=12), - vault_spacing: rng.gen_range(3..=4), + bridge_start_offset: rng.gen_range({ + let min = (5 - height / 3).max(0); + min..=(12 - height).max(min) + }), + vault_spacing, vault_size: *[(3, 16), (1, 4), (1, 4), (1, 4), (5, 32), (5, 32)] .choose(rng) .unwrap(), side_vault_size: *[(4, 5), (7, 10), (7, 10), (13, 20)].choose(rng).unwrap(), - holes: rng.gen_bool(0.5), + holes: vault_spacing > 4 && rng.gen_bool(0.8), } } } @@ -53,25 +57,32 @@ impl BridgeKind { start: Vec3, center: Vec3, end: Vec3, + + name: &str, ) -> BridgeKind { let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); + let height = end.z - start.z; + let down = start.z - center.z; (0..=3) .filter_map(|bridge| match bridge { - 0 if end.z - start.z > 17 => Some(BridgeKind::Tower(match rng.gen_range(0..=2) { + 0 if height >= 16 => Some(BridgeKind::Tower(match rng.gen_range(0..=2) { 0 => RoofKind::Crenelated, _ => RoofKind::Hipped, })), 1 if len < 40 => Some(BridgeKind::Short), - 2 if end.z - start.z < 5 && start.z - center.z < 16 => Some( - BridgeKind::HeightenedViaduct(HeightenedViaduct::random(rng)), - ), - 3 if end.z - start.z < 9 && start.z - center.z > 10 => Some(BridgeKind::HangBridge), + 2 if height < 15 && down < 16 => Some(BridgeKind::HeightenedViaduct( + HeightenedViaduct::random(rng, height), + )), + 3 if height < 9 && down > 10 => Some(BridgeKind::HangBridge), _ => None, }) .collect::>() .into_iter() .choose(rng) - .unwrap_or(BridgeKind::Flat) + .unwrap_or_else(|| { + println!("Pick: {name}\n\tlen: {len}\n\theight: {height}\n\tdown: {down}\n"); + BridgeKind::Flat + }) } fn width(&self) -> i32 { @@ -190,59 +201,89 @@ fn render_short(bridge: &Bridge, painter: &Painter) { fn render_flat(bridge: &Bridge, painter: &Painter) { let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50))); + let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130))); - let dz = bridge.end.z - bridge.start.z; - let orthogonal = bridge.dir.orthogonal().to_vec2(); + let orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); let forward = bridge.dir.to_vec2(); - let inset = 5; - let len = (bridge.end.xy() - bridge.start.xy()) - .map(|e| e.abs()) - .reduce_max(); - let upset = bridge.end.z - bridge.start.z; + let height = bridge.end.z - bridge.start.z; - let size = tweak!(8); - let hole = painter - .cylinder(aabb( - bridge.center.with_z(bridge.end.z - 3 - bridge.width() * 2) - Vec2::broadcast(size), - bridge.center.with_z(bridge.end.z + 2) + Vec2::broadcast(size), - )) - .rotate_about( - bridge.dir.orthogonal().from_z_mat3(), - bridge - .center - .as_() - .with_z(bridge.end.z as f32 - 1.5 - bridge.width() as f32), + let bridge_width = bridge.width(); + let side = orthogonal * bridge_width; + + let aabr = Aabr { + min: bridge.start.xy() - side, + max: bridge.end.xy() + side, + } + .made_valid(); + + let [ramp_aabr, aabr] = bridge.dir.split_aabr(aabr, height); + + let ramp_prim = |ramp_aabr: Aabr, offset: i32| { + painter + .aabb(aabb( + ramp_aabr.min.with_z(bridge.start.z - 10 + offset), + ramp_aabr.max.with_z(bridge.start.z - 1 + offset), + )) + .union(painter.ramp( + aabb( + ramp_aabr.min.with_z(bridge.start.z + offset), + ramp_aabr.max.with_z(bridge.end.z + offset), + ), + height + 1, + bridge.dir, + )) + }; + + ramp_prim(ramp_aabr, 1).fill(light_rock.clone()); + + let ramp_aabr = orth_dir.trim_aabr(ramp_aabr, 1); + ramp_prim(ramp_aabr, 5).clear(); + ramp_prim(ramp_aabr, 0).fill(rock.clone()); + + let vault_width = 12; + let vault_offset = 5; + let bridge_thickness = 4; + + let [vault, _] = bridge.dir.split_aabr(aabr, vault_width); + + let len = bridge.dir.select(aabr.size()); + let true_offset = vault_width + vault_offset; + let n = len / true_offset; + let p = len / n; + + let holes = painter + .vault( + aabb( + vault.min.with_z(bridge.center.z - 20), + vault.max.with_z(bridge.end.z - bridge_thickness - 1), + ), + orth_dir, ) - .scale( - bridge.dir.abs().to_vec3().as_() - * ((len as f32 - upset as f32 * tweak!(2.0)) / (size as f32 * 2.0) - 1.0) - + 1.0, - ); + .repeat((forward * p).with_z(0), n as u32); painter - .ramp( - aabb( - bridge.start.with_z(bridge.start.z - inset) - - orthogonal * bridge.width() - - forward * inset, - bridge.start.with_z(bridge.end.z) + orthogonal * bridge.width() + forward * dz, - ), - dz + inset + 1, - bridge.dir, - ) - .union( - painter - .aabb(aabb( - bridge.start.with_z(bridge.end.z - 3 - size) - orthogonal * bridge.width() - + forward * dz, - bridge.start.with_z(bridge.end.z) - + orthogonal * bridge.width() - + forward * forward * (bridge.end.xy() - bridge.start.xy()), - )) - .without(hole), - ) - .fill(rock); + .aabb(aabb( + aabr.min.with_z(bridge.center.z - 10), + aabr.max.with_z(bridge.end.z + 1), + )) + .without(holes) + .fill(light_rock); + + let aabr = orth_dir.trim_aabr(aabr, 1); + painter + .aabb(aabb( + aabr.min.with_z(bridge.end.z + 1), + aabr.max.with_z(bridge.end.z + 8), + )).clear(); + + painter + .aabb(aabb( + aabr.min.with_z(bridge.end.z), + aabr.max.with_z(bridge.end.z), + )).fill(rock); } fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &HeightenedViaduct) { @@ -262,12 +303,6 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten let bridge_start_z = bridge.end.z + data.bridge_start_offset; let bridge_top = bridge_start_z + len / slope_inv / 2; - // painter.ramp(aabb( - // bridge.start - side, - // (bridge.start.xy() + side + forward * (bridge_start_z - - // bridge.start.z)).with_z(bridge_start_z), ), bridge_start_z - - // bridge.start.z, bridge.dir).fill(rock); - let bridge_width = bridge.width(); let side = orthogonal * bridge_width; @@ -415,8 +450,8 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten } } - bridge_prim(bridge_width).without(remove).fill(rock.clone()); - b.translate(-Vec3::unit_z()).fill(light_rock.clone()); + bridge_prim(bridge_width).without(remove).fill(rock); + b.translate(-Vec3::unit_z()).fill(light_rock); br.translate(Vec3::unit_z() * 5) .without(br.translate(-Vec3::unit_z())) @@ -625,7 +660,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { (tower_aabr.min - 1).with_z(tower_end + 1), (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size), )) - .fill(wood.clone()); + .fill(wood); }, } @@ -650,14 +685,14 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { painter .vault( aabb( - (start + offset - size).with_z(bridge.center.z - 10), - (start + offset + size).with_z(bridge.end.z - 3), + (start + offset / 2 - size).with_z(bridge.center.z - 10), + (start + offset / 2 + size).with_z(bridge.end.z - 3), ), orth_dir, ) .repeat(offset.with_z(0), n as u32), ) - .fill(rock.clone()); + .fill(rock); painter .aabb(aabb( @@ -740,8 +775,10 @@ fn render_hang(bridge: &Bridge, painter: &Painter) { let xsqr = (x * x) as f32; let hsqr = (h * h) as f32; - let w = ((xsqr + (xsqr * (4.0 * hsqr + xsqr)).sqrt()) / 2.0).sqrt().ceil() + 1.0; - + let w = ((xsqr + (xsqr * (4.0 * hsqr + xsqr)).sqrt()) / 2.0) + .sqrt() + .ceil() + + 1.0; let bottom = top - (h - (hsqr - hsqr * x as f32 / w).sqrt().ceil() as i32); @@ -756,7 +793,10 @@ fn render_hang(bridge: &Bridge, painter: &Painter) { ), orth_dir, ) - .intersect(painter.aabb(aabb(aabr.min.with_z(bottom), aabr.max.with_z(bottom + h * 2)))); + .intersect(painter.aabb(aabb( + aabr.min.with_z(bottom), + aabr.max.with_z(bottom + h * 2), + ))); cylinder.fill(wood.clone()); @@ -773,14 +813,34 @@ fn render_hang(bridge: &Bridge, painter: &Painter) { .translate(Vec3::unit_z()) .fill(Fill::Sprite(SpriteKind::Rope)); - edges.translate(Vec3::unit_z() * 2).fill(wood.clone()); + edges.translate(Vec3::unit_z() * 2).fill(wood); let column_height = 3; let column_range = top..=top + column_height; - painter.column(bridge.dir.select_aabr_with(ramp_f, ramp_f.min), column_range.clone()).fill(rock.clone()); - painter.column(bridge.dir.select_aabr_with(ramp_f, ramp_f.max), column_range.clone()).fill(rock.clone()); - painter.column((-bridge.dir).select_aabr_with(ramp_b, ramp_b.min), column_range.clone()).fill(rock.clone()); - painter.column((-bridge.dir).select_aabr_with(ramp_b, ramp_b.max), column_range.clone()).fill(rock.clone()); + painter + .column( + bridge.dir.select_aabr_with(ramp_f, ramp_f.min), + column_range.clone(), + ) + .fill(rock.clone()); + painter + .column( + bridge.dir.select_aabr_with(ramp_f, ramp_f.max), + column_range.clone(), + ) + .fill(rock.clone()); + painter + .column( + (-bridge.dir).select_aabr_with(ramp_b, ramp_b.min), + column_range.clone(), + ) + .fill(rock.clone()); + painter + .column( + (-bridge.dir).select_aabr_with(ramp_b, ramp_b.max), + column_range, + ) + .fill(rock); } pub struct Bridge { @@ -852,7 +912,7 @@ impl Bridge { let center = (start.xy() + end.xy()) / 2; let center = center.with_z(land.get_alt_approx(center) as i32); - let bridge = BridgeKind::random(rng, start, center, end); + let bridge = BridgeKind::random(rng, start, center, end, site.name()); Self { start, end, diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 89b64de88f..e5af183004 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -221,7 +221,10 @@ impl Dir { } } - pub fn split_aabr(self, aabr: Aabr, offset: T) -> [Aabr; 2] where T: Copy + PartialOrd + Add + Sub { + pub fn split_aabr(self, aabr: Aabr, offset: T) -> [Aabr; 2] + where + T: Copy + PartialOrd + Add + Sub, + { match self { Dir::X => aabr.split_at_x(aabr.min.x + offset), Dir::Y => aabr.split_at_y(aabr.min.y + offset), @@ -236,6 +239,13 @@ impl Dir { } } + pub fn trim_aabr(self, aabr: Aabr, offset: i32) -> Aabr { + Aabr { + min: aabr.min + self.abs().to_vec2() * offset, + max: aabr.max - self.abs().to_vec2() * offset, + } + } + pub fn extend_aabr(self, aabr: Aabr, amount: i32) -> Aabr { match self { Dir::X => Aabr { From 17cd8636057672a4e403258ea33c84d45a687db2 Mon Sep 17 00:00:00 2001 From: IsseW Date: Sun, 30 Oct 2022 02:21:24 +0200 Subject: [PATCH 09/14] tuning --- world/src/civ/mod.rs | 7 +-- world/src/site2/mod.rs | 2 +- world/src/site2/plot/bridge.rs | 82 ++++++++++++++++++++-------------- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 0fb7f2b0a8..b34b39931a 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1246,7 +1246,8 @@ fn walk_in_dir( a: Vec2, dir: Vec2, ) -> Option<(Vec2, f32)> { - if let Some(p) = get_bridge(a).filter(|p| (a - p).map(|e| e.signum()) == dir) { + if let Some(p) = get_bridge(a).filter(|p| (p - a).map(|e| e.signum()) == dir) { + // Traversing an existing bridge has no cost. Some((p, 0.0)) } else if loc_suitable_for_walking(sim, a + dir) { let a_chunk = sim.get(a)?; @@ -1261,9 +1262,9 @@ fn walk_in_dir( 3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics }; Some((a + dir, 1.0 + hill_cost + water_cost + wild_cost)) - } else if NEIGHBORS.iter().all(|p| get_bridge(a + p).is_none()) && (dir.x == 0 || dir.y == 0) { + } else if dir.x == 0 || dir.y == 0 { (4..=5).find_map(|i| { - loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, 400.0 + (i - 4) as f32 * 10.0)) + loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, 120.0 + (i - 4) as f32 * 10.0)) }) } else { None diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 0ff0604a10..5736adab32 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -68,7 +68,7 @@ impl Site { // 25 Seems to be big enough for the current scale of 4.0 25 } else { - 1 + 5 }) * TILE_SIZE as i32) as f32 } diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 0757390d2e..cc853b02d4 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -26,7 +26,7 @@ struct HeightenedViaduct { impl HeightenedViaduct { fn random(rng: &mut impl Rng, height: i32) -> Self { - let vault_spacing = *[3, 4, 5, 7].choose(rng).unwrap(); + let vault_spacing = *[3, 4, 5, 6].choose(rng).unwrap(); Self { slope_inv: rng.gen_range(6..=8), bridge_start_offset: rng.gen_range({ @@ -38,7 +38,7 @@ impl HeightenedViaduct { .choose(rng) .unwrap(), side_vault_size: *[(4, 5), (7, 10), (7, 10), (13, 20)].choose(rng).unwrap(), - holes: vault_spacing > 4 && rng.gen_bool(0.8), + holes: vault_spacing >= 4 && vault_spacing % 2 == 0 && rng.gen_bool(0.8), } } } @@ -55,34 +55,44 @@ impl BridgeKind { fn random( rng: &mut impl Rng, start: Vec3, + start_dist: i32, center: Vec3, end: Vec3, + end_dist: i32, + water_alt: i32, name: &str, ) -> BridgeKind { let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); let height = end.z - start.z; - let down = start.z - center.z; - (0..=3) + let down = start.z - water_alt; + let res = (0..=4) .filter_map(|bridge| match bridge { 0 if height >= 16 => Some(BridgeKind::Tower(match rng.gen_range(0..=2) { 0 => RoofKind::Crenelated, _ => RoofKind::Hipped, })), - 1 if len < 40 => Some(BridgeKind::Short), - 2 if height < 15 && down < 16 => Some(BridgeKind::HeightenedViaduct( + 1 if len < 60 => { println!("possible short"); Some(BridgeKind::Short)}, + 2 if len >= 50 && height < 13 && down < 20 && ((start_dist > 13 && end_dist > 13) || (start_dist - end_dist).abs() < 6) => Some(BridgeKind::HeightenedViaduct( HeightenedViaduct::random(rng, height), )), - 3 if height < 9 && down > 10 => Some(BridgeKind::HangBridge), + 3 if height < 10 && down > 10 => Some(BridgeKind::HangBridge), + 4 if down > 8 => Some(BridgeKind::Flat), _ => None, }) .collect::>() .into_iter() .choose(rng) - .unwrap_or_else(|| { - println!("Pick: {name}\n\tlen: {len}\n\theight: {height}\n\tdown: {down}\n"); - BridgeKind::Flat - }) + .unwrap_or_else(|| BridgeKind::Flat); + + match &res { + BridgeKind::Flat => println!("Flat {name}"), + BridgeKind::Tower(_) => println!("Tower {name}"), + BridgeKind::Short => println!("Short {name}"), + BridgeKind::HeightenedViaduct(_) => println!("HeightenedViaduct {name}"), + BridgeKind::HangBridge => println!("HangBridge {name}"), + } + res } fn width(&self) -> i32 { @@ -394,14 +404,6 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten orth_dir, ); - let mut rng = thread_rng(); - if rng.gen_bool(0.1) { - painter.spawn( - EntityInfo::at(c.with_z(bridge.center.z).as_()) - .with_asset_expect("common.entity.wild.aggressive.swamp_troll", &mut rng), - ); - } - if side_vault * 2 + side_vault_offset < len / 2 + 5 { remove = remove.union( painter @@ -457,6 +459,7 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten .without(br.translate(-Vec3::unit_z())) .clear(); + /* let place_lights = |center: Vec3| { painter.sprite( orth_dir @@ -497,6 +500,17 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten for i in 0..num_lights { place_lights(i); } + */ + + // Small chance to spawn a troll. + let mut rng = thread_rng(); + if rng.gen_bool(0.1) { + painter.spawn( + EntityInfo::at(c.with_z(bridge.center.z).as_()) + .with_asset_expect("common.entity.wild.aggressive.swamp_troll", &mut rng), + ); + } + } fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { @@ -864,30 +878,30 @@ impl Bridge { let start = site.tile_wpos(start); let end = site.tile_wpos(end); - let min_water_dist = 5; + let min_water_dist = 6; let find_edge = |start: Vec2, end: Vec2| { let mut test_start = start; let dir = Dir::from_vector(end - start).to_vec2(); let mut last_alt = if let Some(col) = land.column_sample(start, index) { col.alt as i32 } else { - return test_start.with_z(land.get_alt_approx(start) as i32); + return (test_start.with_z(land.get_alt_approx(start) as i32), i32::MAX); }; let mut step = 0; loop { if let Some(sample) = land.column_sample(test_start + step * dir, index) { let alt = sample.alt as i32; - if last_alt - alt > 1 + (step + 3) / 4 + let water_dist = sample.water_dist.unwrap_or(16.0) as i32; + if last_alt - alt > 1 + (step + 2) / 3 || sample.riverless_alt - sample.alt > 2.0 { - break test_start.with_z(last_alt); + break (test_start.with_z(last_alt), water_dist); } else { - let water_dist = sample.water_dist.unwrap_or(16.0) as i32; test_start += step * dir; if water_dist <= min_water_dist { - break test_start.with_z(alt); + break (test_start.with_z(alt), water_dist); } step = water_dist - min_water_dist; @@ -895,24 +909,26 @@ impl Bridge { last_alt = alt; } } else { - break test_start.with_z(last_alt); + break (test_start.with_z(last_alt), i32::MAX); } } }; - let test_start = find_edge(start, end); + let (test_start, start_dist) = find_edge(start, end); - let test_end = find_edge(end, start); + let (test_end, end_dist) = find_edge(end, start); - let (start, end) = if test_start.z < test_end.z { - (test_start, test_end) + let (start, start_dist, end, end_dist) = if test_start.z < test_end.z { + (test_start, start_dist, test_end, end_dist) } else { - (test_end, test_start) + (test_end, end_dist, test_start, start_dist) }; let center = (start.xy() + end.xy()) / 2; - let center = center.with_z(land.get_alt_approx(center) as i32); - let bridge = BridgeKind::random(rng, start, center, end, site.name()); + let col = land.column_sample(center, index).unwrap(); + let center = center.with_z(col.alt as i32); + let water_alt = col.water_level as i32; + let bridge = BridgeKind::random(rng, start, start_dist, center, end, end_dist, water_alt, site.name()); Self { start, end, From dd8c429cfcde231e0b186f54c95f02c6fe4e46ae Mon Sep 17 00:00:00 2001 From: IsseW Date: Thu, 10 Nov 2022 10:36:39 +0100 Subject: [PATCH 10/14] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d3d837ec5..b470bba410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Musical instruments can now be crafted, looted and played - NPCs now move to their target's last known position. - Experience bar below the hotbar +- Bridges. ### Changed From dda54bcb01018a8eddc62c6469694ee82a2459a9 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 29 Nov 2022 21:06:59 +0100 Subject: [PATCH 11/14] small fixes --- world/src/civ/mod.rs | 3 ++- world/src/site2/gen.rs | 14 ++++++---- world/src/site2/plot/bridge.rs | 48 ++++++++++++++++------------------ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index b34b39931a..e00e407078 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1264,7 +1264,8 @@ fn walk_in_dir( Some((a + dir, 1.0 + hill_cost + water_cost + wild_cost)) } else if dir.x == 0 || dir.y == 0 { (4..=5).find_map(|i| { - loc_suitable_for_walking(sim, a + dir * i).then(|| (a + dir * i, 120.0 + (i - 4) as f32 * 10.0)) + loc_suitable_for_walking(sim, a + dir * i) + .then(|| (a + dir * i, 120.0 + (i - 4) as f32 * 10.0)) }) } else { None diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 8b6aaae367..f6657bc52a 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -1073,10 +1073,13 @@ impl Painter { pub fn vault(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { let h = dir.orthogonal().select(Vec3::from(aabb.size()).xy()); - let mut prim = self.horizontal_cylinder(Aabb { - min: aabb.min.with_z(aabb.max.z - h), - max: aabb.max, - }, dir); + let mut prim = self.horizontal_cylinder( + Aabb { + min: aabb.min.with_z(aabb.max.z - h), + max: aabb.max, + }, + dir, + ); if aabb.size().d < h { prim = prim.intersect(self.aabb(aabb)); @@ -1085,7 +1088,8 @@ impl Painter { self.aabb(Aabb { min: aabb.min, max: aabb.max.with_z(aabb.max.z - h / 2), - }).union(prim) + }) + .union(prim) } /// Place aabbs around another aabb in a symmetric and distributed manner. diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index cc853b02d4..12c457ce5f 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -56,26 +56,30 @@ impl BridgeKind { rng: &mut impl Rng, start: Vec3, start_dist: i32, - center: Vec3, end: Vec3, end_dist: i32, water_alt: i32, - - name: &str, ) -> BridgeKind { let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max(); let height = end.z - start.z; let down = start.z - water_alt; - let res = (0..=4) + (0..=4) .filter_map(|bridge| match bridge { 0 if height >= 16 => Some(BridgeKind::Tower(match rng.gen_range(0..=2) { 0 => RoofKind::Crenelated, _ => RoofKind::Hipped, })), - 1 if len < 60 => { println!("possible short"); Some(BridgeKind::Short)}, - 2 if len >= 50 && height < 13 && down < 20 && ((start_dist > 13 && end_dist > 13) || (start_dist - end_dist).abs() < 6) => Some(BridgeKind::HeightenedViaduct( - HeightenedViaduct::random(rng, height), - )), + 1 if len < 60 => Some(BridgeKind::Short), + 2 if len >= 50 + && height < 13 + && down < 20 + && ((start_dist > 13 && end_dist > 13) + || (start_dist - end_dist).abs() < 6) => + { + Some(BridgeKind::HeightenedViaduct(HeightenedViaduct::random( + rng, height, + ))) + }, 3 if height < 10 && down > 10 => Some(BridgeKind::HangBridge), 4 if down > 8 => Some(BridgeKind::Flat), _ => None, @@ -83,16 +87,7 @@ impl BridgeKind { .collect::>() .into_iter() .choose(rng) - .unwrap_or_else(|| BridgeKind::Flat); - - match &res { - BridgeKind::Flat => println!("Flat {name}"), - BridgeKind::Tower(_) => println!("Tower {name}"), - BridgeKind::Short => println!("Short {name}"), - BridgeKind::HeightenedViaduct(_) => println!("HeightenedViaduct {name}"), - BridgeKind::HangBridge => println!("HangBridge {name}"), - } - res + .unwrap_or(BridgeKind::Flat) } fn width(&self) -> i32 { @@ -287,13 +282,15 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { .aabb(aabb( aabr.min.with_z(bridge.end.z + 1), aabr.max.with_z(bridge.end.z + 8), - )).clear(); + )) + .clear(); painter .aabb(aabb( aabr.min.with_z(bridge.end.z), aabr.max.with_z(bridge.end.z), - )).fill(rock); + )) + .fill(rock); } fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &HeightenedViaduct) { @@ -510,7 +507,6 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten .with_asset_expect("common.entity.wild.aggressive.swamp_troll", &mut rng), ); } - } fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { @@ -878,14 +874,17 @@ impl Bridge { let start = site.tile_wpos(start); let end = site.tile_wpos(end); - let min_water_dist = 6; + let min_water_dist = 5; let find_edge = |start: Vec2, end: Vec2| { let mut test_start = start; let dir = Dir::from_vector(end - start).to_vec2(); let mut last_alt = if let Some(col) = land.column_sample(start, index) { col.alt as i32 } else { - return (test_start.with_z(land.get_alt_approx(start) as i32), i32::MAX); + return ( + test_start.with_z(land.get_alt_approx(start) as i32), + i32::MAX, + ); }; let mut step = 0; loop { @@ -897,7 +896,6 @@ impl Bridge { { break (test_start.with_z(last_alt), water_dist); } else { - test_start += step * dir; if water_dist <= min_water_dist { @@ -928,7 +926,7 @@ impl Bridge { let col = land.column_sample(center, index).unwrap(); let center = center.with_z(col.alt as i32); let water_alt = col.water_level as i32; - let bridge = BridgeKind::random(rng, start, start_dist, center, end, end_dist, water_alt, site.name()); + let bridge = BridgeKind::random(rng, start, start_dist, end, end_dist, water_alt); Self { start, end, From c81fbf50c41c41e57fa8934be285b9eb39fd495c Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 30 Nov 2022 00:50:41 +0100 Subject: [PATCH 12/14] Fix cylinder primitive and better ramp function --- world/src/site2/gen.rs | 16 +++++- world/src/site2/plot/bridge.rs | 57 ++++++++++--------- world/src/site2/plot/desert_city_multiplot.rs | 5 +- world/src/site2/plot/sea_chapel.rs | 12 ++-- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index f6657bc52a..d1bf51aea9 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -289,7 +289,8 @@ impl Fill { - ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32 }, Primitive::Cylinder(aabb) => { - let fpos = pos.as_::().xy() - aabb.as_::().center().xy(); + // Add 0.5 since the aabb is exclusive. + let fpos = pos.as_::().xy() - aabb.as_::().center().xy() + 0.5; let size = Vec3::from(aabb.size().as_::()).xy(); (aabb.min.z..aabb.max.z).contains(&pos.z) && (2.0 * fpos / size).magnitude_squared() <= 1.0 @@ -980,11 +981,20 @@ impl Painter { /// Returns a `PrimitiveRef` of an Aabb with a slope cut into it. The /// `inset` governs the slope. The `dir` determines which direction the /// ramp points. - pub fn ramp(&self, aabb: Aabb, inset: i32, dir: Dir) -> PrimitiveRef { + pub fn ramp_inset(&self, aabb: Aabb, inset: i32, dir: Dir) -> PrimitiveRef { let aabb = aabb.made_valid(); self.prim(Primitive::Ramp { aabb, inset, dir }) } + pub fn ramp(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { + let aabb = aabb.made_valid(); + self.prim(Primitive::Ramp { + aabb, + inset: dir.select((aabb.size().w, aabb.size().h)), + dir, + }) + } + /// Returns a `PrimitiveRef` of a triangular prism with the base being /// vertical. A gable is a tent shape. The `inset` governs the slope of /// the gable. The `dir` determines which way the gable points. @@ -1070,6 +1080,7 @@ impl Painter { /// | | / /// |_____|/ /// ``` + /// A horizontal half cylinder on top of an `Aabb`. pub fn vault(&self, aabb: Aabb, dir: Dir) -> PrimitiveRef { let h = dir.orthogonal().select(Vec3::from(aabb.size()).xy()); @@ -1150,7 +1161,6 @@ impl Painter { .with_z(z + 1), } .made_valid(), - stair_len + 1, right, ); diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 12c457ce5f..8e668b8ae5 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -108,7 +108,7 @@ fn aabb(min: Vec3, max: Vec3) -> Aabb { fn render_short(bridge: &Bridge, painter: &Painter) { let (bridge_fill, edge_fill) = match bridge.biome { - BiomeKind::Desert | BiomeKind::Savannah => ( + BiomeKind::Desert => ( Fill::Block(Block::new(BlockKind::Rock, Rgb::new(212, 191, 142))), Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(190))), ), @@ -130,44 +130,45 @@ fn render_short(bridge: &Bridge, painter: &Painter) { .reduce_max(); let inset = 4; - let height = bridge.end.z + len / 3 - inset; + let top = bridge.end.z + (len / 5).max(8) - inset; let side = orthogonal * bridge_width; let remove = painter.vault( aabb( (bridge.start.xy() - side + forward * inset).with_z(bridge.start.z), - (bridge.end.xy() + side - forward * inset).with_z(height), + (bridge.end.xy() + side - forward * inset).with_z(top - 2), ), orth_dir, ); - let ramp_in = len / 3; - - let top = height + 2; - - let outset = 7; + // let outset = 7; let up_ramp = |point: Vec3, dir: Dir, side_len: i32| { let forward = dir.to_vec2(); let side = dir.orthogonal().to_vec2() * side_len; - painter.ramp( - aabb( - (point.xy() - side - forward * (top - point.z - ramp_in + outset)) - .with_z(bridge.start.z), - (point.xy() + side + forward * ramp_in).with_z(top), - ), - top - point.z + 1 + outset, - dir, - ) + let ramp_in = top - point.z; + painter + .ramp( + aabb( + point - side, + (point.xy() + side + forward * ramp_in).with_z(top), + ), + dir, + ) + .union(painter.aabb(aabb( + (point - side).with_z(point.z - 4), + point + side + forward * ramp_in, + ))) }; let bridge_prim = |side_len: i32| { let side = orthogonal * side_len; painter .aabb(aabb( - (bridge.start.xy() - side + forward * ramp_in).with_z(bridge.start.z), - (bridge.end.xy() + side - forward * ramp_in).with_z(top), + (bridge.start.xy() - side + forward * (top - bridge.start.z)) + .with_z(bridge.start.z), + (bridge.end.xy() + side - forward * (top - bridge.end.z)).with_z(top), )) .union(up_ramp(bridge.start, bridge.dir, side_len).union(up_ramp( bridge.end, @@ -178,18 +179,20 @@ fn render_short(bridge: &Bridge, painter: &Painter) { let b = bridge_prim(bridge_width); + /* let t = 4; b.union( painter.aabb(aabb( - (bridge.start.xy() - side - forward * (top - bridge.start.z - ramp_in + outset)) + (bridge.start.xy() - side - forward * (top - bridge.start.z)) .with_z(bridge.start.z - t), - (bridge.end.xy() + side + forward * (top - bridge.end.z - ramp_in + outset)) + (bridge.end.xy() + side + forward * (top - bridge.end.z)) .with_z(bridge.start.z), )), ) .translate(Vec3::new(0, 0, t)) .without(b) .clear(); + */ b.without(remove).fill(bridge_fill); @@ -198,8 +201,8 @@ fn render_short(bridge: &Bridge, painter: &Painter) { prim.translate(Vec3::unit_z()) .without(prim) .without(painter.aabb(aabb( - bridge.start - side - forward * outset, - (bridge.end.xy() + side + forward * outset).with_z(top + 1), + bridge.start - side - forward, + (bridge.end.xy() + side + forward).with_z(top + 1), ))) .fill(edge_fill); } @@ -237,7 +240,6 @@ fn render_flat(bridge: &Bridge, painter: &Painter) { ramp_aabr.min.with_z(bridge.start.z + offset), ramp_aabr.max.with_z(bridge.end.z + offset), ), - height + 1, bridge.dir, )) }; @@ -340,7 +342,7 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten let ramp_in_aabr = |aabr: Aabr, dir: Dir, zmin, zmax| { let inset = dir.select(aabr.size()); - painter.ramp( + painter.ramp_inset( aabb(aabr.min.with_z(zmin), aabr.max.with_z(zmax)), inset, dir, @@ -565,7 +567,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) { painter .aabb(ramp_aabb) - .without(painter.ramp(ramp_aabb, ramp_height, -bridge.dir)) + .without(painter.ramp(ramp_aabb, -bridge.dir)) .clear(); let c = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()); @@ -755,7 +757,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) { )) .fill(rock.clone()); painter - .ramp( + .ramp_inset( aabb(ramp_f.min.with_z(bridge.start.z), ramp_f.max.with_z(top)), top - bridge.start.z + 1, bridge.dir, @@ -772,7 +774,6 @@ fn render_hang(bridge: &Bridge, painter: &Painter) { painter .ramp( aabb(ramp_b.min.with_z(bridge.end.z), ramp_b.max.with_z(top)), - top - bridge.end.z, -bridge.dir, ) .fill(rock.clone()); diff --git a/world/src/site2/plot/desert_city_multiplot.rs b/world/src/site2/plot/desert_city_multiplot.rs index 3d2c86f2c7..5bcbd03e89 100644 --- a/world/src/site2/plot/desert_city_multiplot.rs +++ b/world/src/site2/plot/desert_city_multiplot.rs @@ -349,7 +349,7 @@ impl Structure for DesertCityMultiPlot { .clear(); // Stairs for each storey painter - .ramp( + .ramp_inset( Aabb { min: Vec2::new(center.x - room_length, center.y - room_length + 1) .with_z(floor_level), @@ -1016,7 +1016,7 @@ impl Structure for DesertCityMultiPlot { .clear(); // Stairs for each storey painter - .ramp( + .ramp_inset( Aabb { min: Vec2::new( subplot_center.x - room_length, @@ -1628,7 +1628,6 @@ impl Structure for DesertCityMultiPlot { ) .with_z(base + tower_height + 1), }, - 2, Dir::NegX, ) .fill(sandstone.clone()); diff --git a/world/src/site2/plot/sea_chapel.rs b/world/src/site2/plot/sea_chapel.rs index 34dafa7f36..b5b79ebb94 100644 --- a/world/src/site2/plot/sea_chapel.rs +++ b/world/src/site2/plot/sea_chapel.rs @@ -241,7 +241,7 @@ impl Structure for SeaChapel { }) .fill(white.clone()); painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new( center.x - (diameter / 2) - 12, @@ -275,7 +275,7 @@ impl Structure for SeaChapel { }) .fill(white.clone()); painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new( center.x + (diameter / 2) - 2, @@ -3059,7 +3059,7 @@ impl Structure for SeaChapel { .fill(window_ver.clone()); // chapel main room pulpit stairs1 painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new(center.x - 8, center.y - (diameter / 4) - 2, base - 3), max: Vec3::new(center.x - 3, center.y - (diameter / 4) + 7, base + 2), @@ -3070,7 +3070,7 @@ impl Structure for SeaChapel { .fill(white.clone()); // chapel main room pulpit stairs2 painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new(center.x + 3, center.y - (diameter / 4) - 2, base - 3), max: Vec3::new(center.x + 8, center.y - (diameter / 4) + 7, base + 2), @@ -3081,7 +3081,7 @@ impl Structure for SeaChapel { .fill(white.clone()); // chapel main room pulpit stairs2 painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new(center.x - 8, center.y + (diameter / 4) - 7, base - 3), max: Vec3::new(center.x - 3, center.y + (diameter / 4) + 2, base + 2), @@ -3092,7 +3092,7 @@ impl Structure for SeaChapel { .fill(white.clone()); // chapel main room pulpit stairs4 painter - .ramp( + .ramp_inset( Aabb { min: Vec3::new(center.x + 3, center.y + (diameter / 4) - 7, base - 3), max: Vec3::new(center.x + 8, center.y + (diameter / 4) + 2, base + 2), From 560d5c3153d36a3b4acf08345a0078daaf3515e4 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 30 Nov 2022 00:51:27 +0100 Subject: [PATCH 13/14] fix doc test --- world/src/site2/util/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index e5af183004..56bc837f64 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -119,19 +119,19 @@ impl Dir { /// use veloren_world::site2::util::Dir; /// let dir = Dir::X; /// - /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// assert_eq!(dir.to_mat3() * Vec3::new(1, 0, 0), dir.to_vec3()); /// /// let dir = Dir::NegX; /// - /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// assert_eq!(dir.to_mat3() * Vec3::new(1, 0, 0), dir.to_vec3()); /// /// let dir = Dir::Y; /// - /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// assert_eq!(dir.to_mat3() * Vec3::new(1, 0, 0), dir.to_vec3()); /// /// let dir = Dir::NegY; /// - /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// assert_eq!(dir.to_mat3() * Vec3::new(1, 0, 0), dir.to_vec3()); /// ``` pub fn to_mat3(self) -> Mat3 { match self { From 02eadc3002e83fac658f8fde58f37829351bdc5f Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 30 Nov 2022 00:52:14 +0100 Subject: [PATCH 14/14] add doc comment --- world/src/site2/util/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 56bc837f64..b43266c8d6 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -142,6 +142,8 @@ impl Dir { } } + /// Creates a matrix that tranforms an upwards facing vector to this + /// direction. pub fn from_z_mat3(self) -> Mat3 { match self { Dir::X => Mat3::new(0, 0, -1, 0, 1, 0, 1, 0, 0),