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 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/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 67938150b4..e00e407078 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, @@ -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,7 @@ impl Civs { SiteKind::GiantTree => (12i32, 8.0), SiteKind::Gnarling => (16i32, 10.0), SiteKind::Citadel => (16i32, 0.0), + SiteKind::Bridge(_, _) => (0, 0.0), }; let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { @@ -246,59 +249,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, - )), + 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]; @@ -980,12 +997,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 +1043,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 +1058,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 +1117,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 +1201,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 +1240,33 @@ 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| (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)?; 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, 120.0 + (i - 4) as f32 * 10.0)) + }) } else { None } @@ -1184,8 +1274,11 @@ 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().any(|n| { + sim.get(loc + *n) + .map_or(false, |chunk| chunk.river.near_water()) + }) } else { false } @@ -1312,6 +1405,7 @@ pub enum SiteKind { GiantTree, Gnarling, Citadel, + Bridge(Vec2, Vec2), } impl SiteKind { @@ -1431,6 +1525,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 +1591,7 @@ impl SiteKind { }, SiteKind::Dungeon => on_land(), SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(6.7), + SiteKind::Bridge(_, _) => true, } }) } @@ -1525,6 +1622,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/column/mod.rs b/world/src/column/mod.rs index 6b5da228c7..0d55f142c7 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/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/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/sim/map.rs b/world/src/sim/map.rs index a2b340f55d..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, + site::SiteKind, + IndexRef, CONFIG, }; use common::{ terrain::{ @@ -71,6 +72,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 +104,7 @@ pub fn sample_pos( river_kind, spline_derivative, is_path, + is_bridge, ) = sampler .get(pos) .map(|sample| { @@ -116,6 +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 + { + matches!(bridge.plot(plot).kind, crate::site2::PlotKind::Bridge(_)) + } else { + false + } + }, + _ => false, + }), ) }) .unwrap_or(( @@ -129,6 +147,7 @@ pub fn sample_pos( None, Vec2::zero(), false, + false, )); let humidity = humidity.clamp(0.0, 1.0); @@ -246,7 +265,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..92d1983eca 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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/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..d1bf51aea9 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, ops::RangeBounds, sync::Arc}; use vek::*; #[allow(dead_code)] @@ -289,13 +289,11 @@ impl Fill { - ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32 }, Primitive::Cylinder(aabb) => { + // 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) - && (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) @@ -389,10 +387,9 @@ impl Fill { Self::contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub)) }, Primitive::Scale(prim, vec) => { - let center = - Self::get_bounds(tree, *prim).center().as_::() - Vec3::broadcast(0.5); + let center = 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( @@ -970,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. @@ -1052,6 +1072,123 @@ impl Painter { } } + /// ```text + /// ___ + /// / /\ + /// /__/ | + /// / \ | + /// | | / + /// |_____|/ + /// ``` + /// 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()); + + 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)); + } + + self.aabb(Aabb { + min: aabb.min, + max: aabb.max.with_z(aabb.max.z - h / 2), + }) + .union(prim) + } + + /// 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(), + 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 + } + + 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 } @@ -1133,7 +1270,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. @@ -1150,7 +1287,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)) } + 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)) @@ -1172,7 +1311,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 0ac386b461..5736adab32 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, @@ -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 } @@ -988,6 +988,78 @@ impl Site { site } + pub fn generate_bridge( + land: &Land, + index: IndexRef, + 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 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, + }; + + site.create_road( + land, + &mut rng, + 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, + (-bridge.dir).select_aabr_with(aabr, aabr.center()) - bridge.dir.to_vec2(), + (-bridge.dir).select_aabr_with(start_aabr, aabr.center()), + 2, + ); + + 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 +1317,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..8e668b8ae5 --- /dev/null +++ b/world/src/site2/plot/bridge.rs @@ -0,0 +1,956 @@ +use super::*; +use crate::{site2::gen::PrimitiveTransform, Land}; +use common::{ + generation::EntityInfo, + terrain::{BiomeKind, Block, BlockKind}, +}; +use num::integer::Roots; +use rand::prelude::*; +use vek::*; + +use inline_tweak::tweak; + +enum RoofKind { + Crenelated, + 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, height: i32) -> Self { + let vault_spacing = *[3, 4, 5, 6].choose(rng).unwrap(); + Self { + slope_inv: rng.gen_range(6..=8), + 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: vault_spacing >= 4 && vault_spacing % 2 == 0 && rng.gen_bool(0.8), + } + } +} + +enum BridgeKind { + Flat, + Tower(RoofKind), + Short, + HeightenedViaduct(HeightenedViaduct), + HangBridge, +} + +impl BridgeKind { + fn random( + rng: &mut impl Rng, + start: Vec3, + start_dist: i32, + end: Vec3, + end_dist: i32, + water_alt: i32, + ) -> 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; + (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 => 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, + }) + .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 { + let aabb = Aabb { min, max }.made_valid(); + Aabb { + min: aabb.min, + max: aabb.max + 1, + } +} + +fn render_short(bridge: &Bridge, painter: &Painter) { + let (bridge_fill, edge_fill) = match bridge.biome { + BiomeKind::Desert => ( + 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; + + 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 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(top - 2), + ), + orth_dir, + ); + + // 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; + 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 * (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, + -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)) + .with_z(bridge.start.z - t), + (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); + + let prim = bridge_prim(bridge_width + 1); + + prim.translate(Vec3::unit_z()) + .without(prim) + .without(painter.aabb(aabb( + bridge.start - side - forward, + (bridge.end.xy() + side + forward).with_z(top + 1), + ))) + .fill(edge_fill); +} + +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 orth_dir = bridge.dir.orthogonal(); + + let orthogonal = orth_dir.to_vec2(); + let forward = bridge.dir.to_vec2(); + + let height = bridge.end.z - bridge.start.z; + + 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), + ), + 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, + ) + .repeat((forward * p).with_z(0), n as u32); + + painter + .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) { + 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; + + 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_inset( + 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 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()); + 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(remove).fill(rock); + b.translate(-Vec3::unit_z()).fill(light_rock); + + 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); + } + */ + + // 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) { + 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; + + 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_center = bridge.start.xy() + forward * tower_size; + let tower_aabr = Aabr { + 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() - 1; + + 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.dir).select_aabr_with(tower_aabr, tower_aabr.center()); + 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, -bridge.dir)) + .clear(); + + let c = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()); + 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)); + }, + 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); + }, + } + + 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()) + forward; + painter + .aabb(aabb( + (start - orthogonal * bridge_width).with_z(bridge.center.z - 10), + (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z - 1), + )) + .without( + painter + .vault( + aabb( + (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); + + 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; + + 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); + } +} + +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_inset( + 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)), + -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); + + 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, + ) + .fill(rock); +} + +pub struct Bridge { + pub(crate) start: Vec3, + pub(crate) end: Vec3, + pub(crate) dir: Dir, + center: Vec3, + kind: BridgeKind, + biome: BiomeKind, +} + +impl Bridge { + pub fn generate( + land: &Land, + index: IndexRef, + rng: &mut impl Rng, + site: &Site, + start: Vec2, + end: Vec2, + ) -> Self { + let start = site.tile_wpos(start); + let end = site.tile_wpos(end); + + 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, + ); + }; + let mut step = 0; + loop { + if let Some(sample) = land.column_sample(test_start + step * dir, index) { + let alt = sample.alt as i32; + 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), water_dist); + } else { + test_start += step * dir; + + if water_dist <= min_water_dist { + break (test_start.with_z(alt), water_dist); + } + + step = water_dist - min_water_dist; + + last_alt = alt; + } + } else { + break (test_start.with_z(last_alt), i32::MAX); + } + } + }; + + let (test_start, start_dist) = find_edge(start, end); + + let (test_end, end_dist) = find_edge(end, start); + + 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, end_dist, test_start, start_dist) + }; + + let center = (start.xy() + end.xy()) / 2; + 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, end, end_dist, water_alt); + Self { + start, + end, + center, + dir: Dir::from_vector(end.xy() - start.xy()), + 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 { + 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::Short => render_short(self, painter), + BridgeKind::HeightenedViaduct(data) => render_heightened_viaduct(self, painter, data), + BridgeKind::HangBridge => render_hang(self, painter), + } + } +} 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), diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 7a15e5f356..b43266c8d6 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::*; @@ -13,6 +15,8 @@ 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 +68,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), @@ -91,21 +119,21 @@ 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_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 +142,17 @@ 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), + 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 +170,104 @@ 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: impl Into>) -> i32 { + let vec = vec.into(); + 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: 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), + } + } + + /// Returns the side of an aabr that the direction is pointing to + pub fn select_aabr(self, aabr: Aabr) -> T { + 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: 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), + 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: 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), + 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 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 { + 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 {