mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
simple bridges
This commit is contained in:
parent
8a6e79c249
commit
7296843923
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7173,6 +7173,7 @@ dependencies = [
|
||||
"fxhash",
|
||||
"hashbrown 0.12.3",
|
||||
"image",
|
||||
"inline_tweak",
|
||||
"itertools",
|
||||
"kiddo 0.2.4",
|
||||
"lazy_static",
|
||||
|
BIN
assets/voxygen/element/ui/map/buttons/bridge.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/map/buttons/bridge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/map/buttons/bridge_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/map/buttons/bridge_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/ui/map/buttons/bridge_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/map/buttons/bridge_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -148,6 +148,7 @@ pub enum SiteKind {
|
||||
Tree,
|
||||
Gnarling,
|
||||
ChapelSite,
|
||||
Bridge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
||||
|
@ -53,6 +53,8 @@ pub struct Civs {
|
||||
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
pub track_map: DHashMap<Id<Site>, DHashMap<Id<Site>, Id<Track>>>,
|
||||
|
||||
pub bridges: DHashMap<Vec2<i32>, (Vec2<i32>, Id<Site>)>,
|
||||
|
||||
pub sites: Store<Site>,
|
||||
pub caves: Store<CaveInfo>,
|
||||
}
|
||||
@ -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<Site> {
|
||||
const SITE_AREA: Range<usize> = 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<impl Rng>,
|
||||
loc: Vec2<i32>,
|
||||
site_fn: impl FnOnce(Id<Place>) -> Site,
|
||||
) -> Id<Site> {
|
||||
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<impl Rng>,
|
||||
get_bridge: impl Fn(Vec2<i32>) -> Option<Vec2<i32>>,
|
||||
a: Vec2<i32>,
|
||||
b: Vec2<i32>,
|
||||
) -> Option<(Path<Vec2<i32>>, f32)> {
|
||||
const MAX_PATH_ITERS: usize = 100_000;
|
||||
let sim = &ctx.sim;
|
||||
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
||||
let get_bridge = &get_bridge;
|
||||
let neighbors = |l: &Vec2<i32>| {
|
||||
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<i32>, b: &Vec2<i32>| {
|
||||
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<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
||||
let satisfied = |l: &Vec2<i32>| *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<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
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<i32>) -> Option<Vec2<i32>>,
|
||||
a: Vec2<i32>,
|
||||
dir: Vec2<i32>,
|
||||
) -> Option<(Vec2<i32>, 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<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
|
||||
/// Return true if a position is suitable for walking on
|
||||
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> 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<i32>, Vec2<i32>),
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ impl Environment {
|
||||
SiteKind::GiantTree(_) => (),
|
||||
SiteKind::Gnarling(_) => {},
|
||||
SiteKind::ChapelSite(_) => {},
|
||||
SiteKind::Bridge(_) => {},
|
||||
}
|
||||
}
|
||||
if towns.valid() {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1052,6 +1052,122 @@ impl Painter {
|
||||
}
|
||||
}
|
||||
|
||||
/// ```text
|
||||
/// ___
|
||||
/// / /\
|
||||
/// /__/ |
|
||||
/// / \ |
|
||||
/// | | /
|
||||
/// |_____|/
|
||||
/// ```
|
||||
pub fn vault(&self, aabb: Aabb<i32>, 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<i32>, 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_::<f32>().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<i32>,
|
||||
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<i32> { self.render_area }
|
||||
|
||||
|
@ -988,6 +988,68 @@ impl Site {
|
||||
site
|
||||
}
|
||||
|
||||
pub fn generate_bridge(
|
||||
land: &Land,
|
||||
rng: &mut impl Rng,
|
||||
start: Vec2<i32>,
|
||||
end: Vec2<i32>,
|
||||
) -> 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<i32>) -> Vec2<i32> {
|
||||
(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,
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
335
world/src/site2/plot/bridge.rs
Normal file
335
world/src/site2/plot/bridge.rs
Normal file
@ -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<i32>, max: Vec3<i32>) -> Aabb<i32> {
|
||||
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<i32>,
|
||||
end: Vec3<i32>,
|
||||
center: Vec3<i32>,
|
||||
dir: Dir,
|
||||
kind: BridgeKind,
|
||||
pub(crate) width: i32,
|
||||
}
|
||||
|
||||
impl Bridge {
|
||||
pub fn generate(
|
||||
land: &Land,
|
||||
_rng: &mut impl Rng,
|
||||
site: &Site,
|
||||
start: Vec2<i32>,
|
||||
end: Vec2<i32>,
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<i32> {
|
||||
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<i32> {
|
||||
pub fn to_mat3(self) -> Mat3<i32> {
|
||||
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<i32> {
|
||||
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>) -> 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<i32>, other: Vec2<i32>) -> Vec2<i32> {
|
||||
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>) -> 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<i32>, other: Vec2<i32>) -> Vec2<i32> {
|
||||
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<i32>, offset: i32) -> [Aabr<i32>; 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<i32>, amount: i32) -> Aabr<i32> {
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user