mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'isse/bridges' into 'master'
Bridges See merge request veloren/veloren!3680
This commit is contained in:
commit
ef2c1633ed
@ -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
|
- Musical instruments can now be crafted, looted and played
|
||||||
- NPCs now move to their target's last known position.
|
- NPCs now move to their target's last known position.
|
||||||
- Experience bar below the hotbar
|
- Experience bar below the hotbar
|
||||||
|
- Bridges.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7173,6 +7173,7 @@ dependencies = [
|
|||||||
"fxhash",
|
"fxhash",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
"image",
|
"image",
|
||||||
|
"inline_tweak",
|
||||||
"itertools",
|
"itertools",
|
||||||
"kiddo 0.2.4",
|
"kiddo 0.2.4",
|
||||||
"lazy_static",
|
"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-tree = Giant Tree
|
||||||
hud-map-town = Town
|
hud-map-town = Town
|
||||||
hud-map-castle = Castle
|
hud-map-castle = Castle
|
||||||
|
hud-map-bridge = Bridge
|
||||||
hud-map-dungeon = Dungeon
|
hud-map-dungeon = Dungeon
|
||||||
hud-map-difficulty_dungeon =
|
hud-map-difficulty_dungeon =
|
||||||
Dungeon
|
Dungeon
|
||||||
|
@ -148,6 +148,7 @@ pub enum SiteKind {
|
|||||||
Tree,
|
Tree,
|
||||||
Gnarling,
|
Gnarling,
|
||||||
ChapelSite,
|
ChapelSite,
|
||||||
|
Bridge,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -458,6 +458,9 @@ image_ids! {
|
|||||||
mmap_site_castle: "voxygen.element.ui.map.buttons.castle",
|
mmap_site_castle: "voxygen.element.ui.map.buttons.castle",
|
||||||
mmap_site_castle_hover: "voxygen.element.ui.map.buttons.castle_hover",
|
mmap_site_castle_hover: "voxygen.element.ui.map.buttons.castle_hover",
|
||||||
mmap_site_castle_bg: "voxygen.element.ui.map.buttons.castle_bg",
|
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_bg: "voxygen.element.ui.map.buttons.cave_bg",
|
||||||
mmap_site_cave_hover: "voxygen.element.ui.map.buttons.cave_hover",
|
mmap_site_cave_hover: "voxygen.element.ui.map.buttons.cave_hover",
|
||||||
mmap_site_cave: "voxygen.element.ui.map.buttons.cave",
|
mmap_site_cave: "voxygen.element.ui.map.buttons.cave",
|
||||||
|
@ -63,6 +63,9 @@ widget_ids! {
|
|||||||
show_castles_img,
|
show_castles_img,
|
||||||
show_castles_box,
|
show_castles_box,
|
||||||
show_castles_text,
|
show_castles_text,
|
||||||
|
show_bridges_img,
|
||||||
|
show_bridges_box,
|
||||||
|
show_bridges_text,
|
||||||
show_dungeons_img,
|
show_dungeons_img,
|
||||||
show_dungeons_box,
|
show_dungeons_box,
|
||||||
show_dungeons_text,
|
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_towns = self.global_state.settings.interface.map_show_towns;
|
||||||
let show_dungeons = self.global_state.settings.interface.map_show_dungeons;
|
let show_dungeons = self.global_state.settings.interface.map_show_dungeons;
|
||||||
let show_castles = self.global_state.settings.interface.map_show_castles;
|
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_caves = self.global_state.settings.interface.map_show_caves;
|
||||||
let show_trees = self.global_state.settings.interface.map_show_trees;
|
let show_trees = self.global_state.settings.interface.map_show_trees;
|
||||||
let show_peaks = self.global_state.settings.interface.map_show_peaks;
|
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)
|
.graphics_for(state.ids.show_castles_box)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.set(state.ids.show_castles_text, ui);
|
.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
|
// Dungeons
|
||||||
Image::new(self.imgs.mmap_site_dungeon)
|
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)
|
.w_h(20.0, 20.0)
|
||||||
.set(state.ids.show_dungeons_img, ui);
|
.set(state.ids.show_dungeons_img, ui);
|
||||||
if Button::image(if show_dungeons {
|
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::Tree => i18n.get_msg("hud-map-tree"),
|
||||||
SiteKind::Gnarling => i18n.get_msg("hud-map-gnarling"),
|
SiteKind::Gnarling => i18n.get_msg("hud-map-gnarling"),
|
||||||
SiteKind::ChapelSite => i18n.get_msg("hud-map-chapel_Site"),
|
SiteKind::ChapelSite => i18n.get_msg("hud-map-chapel_Site"),
|
||||||
|
SiteKind::Bridge => i18n.get_msg("hud-map-bridge"),
|
||||||
});
|
});
|
||||||
let (difficulty, desc) = match &site.kind {
|
let (difficulty, desc) = match &site.kind {
|
||||||
SiteKind::Town => (None, i18n.get_msg("hud-map-town")),
|
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::Tree => (None, i18n.get_msg("hud-map-tree")),
|
||||||
SiteKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")),
|
SiteKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")),
|
||||||
SiteKind::ChapelSite => (Some(3), i18n.get_msg("hud-map-chapel_site")),
|
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 desc = desc.into_owned() + &get_site_economy(site_rich);
|
||||||
let site_btn = Button::image(match &site.kind {
|
let site_btn = Button::image(match &site.kind {
|
||||||
@ -921,6 +961,7 @@ impl<'a> Widget for Map<'a> {
|
|||||||
5 => self.imgs.mmap_site_mindflayer,
|
5 => self.imgs.mmap_site_mindflayer,
|
||||||
_ => self.imgs.mmap_site_dungeon,
|
_ => self.imgs.mmap_site_dungeon,
|
||||||
},
|
},
|
||||||
|
SiteKind::Bridge => self.imgs.mmap_site_bridge,
|
||||||
})
|
})
|
||||||
.x_y_position_relative_to(
|
.x_y_position_relative_to(
|
||||||
state.ids.map_layers[0],
|
state.ids.map_layers[0],
|
||||||
@ -940,6 +981,7 @@ impl<'a> Widget for Map<'a> {
|
|||||||
5 => self.imgs.mmap_site_mindflayer_hover,
|
5 => self.imgs.mmap_site_mindflayer_hover,
|
||||||
_ => self.imgs.mmap_site_dungeon_hover,
|
_ => self.imgs.mmap_site_dungeon_hover,
|
||||||
},
|
},
|
||||||
|
SiteKind::Bridge => self.imgs.mmap_site_bridge_hover,
|
||||||
})
|
})
|
||||||
.image_color(UI_HIGHLIGHT_0.alpha(fade))
|
.image_color(UI_HIGHLIGHT_0.alpha(fade))
|
||||||
.with_tooltip(
|
.with_tooltip(
|
||||||
@ -962,6 +1004,7 @@ impl<'a> Widget for Map<'a> {
|
|||||||
},
|
},
|
||||||
SiteKind::Cave => TEXT_COLOR,
|
SiteKind::Cave => TEXT_COLOR,
|
||||||
SiteKind::Tree => 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::Castle => show_castles,
|
||||||
SiteKind::Cave => show_caves,
|
SiteKind::Cave => show_caves,
|
||||||
SiteKind::Tree => show_trees,
|
SiteKind::Tree => show_trees,
|
||||||
|
SiteKind::Bridge => show_bridges,
|
||||||
};
|
};
|
||||||
if show_site {
|
if show_site {
|
||||||
let tooltip_visible = site_btn.set_ext(state.ids.mmap_site_icons[i], ui).1;
|
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)
|
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(
|
handle_widget_mouse_events(
|
||||||
|
@ -698,6 +698,7 @@ impl<'a> Widget for MiniMap<'a> {
|
|||||||
SiteKind::Cave => None,
|
SiteKind::Cave => None,
|
||||||
SiteKind::Tree => None,
|
SiteKind::Tree => None,
|
||||||
SiteKind::Gnarling => Some(0),
|
SiteKind::Gnarling => Some(0),
|
||||||
|
SiteKind::Bridge => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Image::new(match &site.kind {
|
Image::new(match &site.kind {
|
||||||
@ -708,6 +709,7 @@ impl<'a> Widget for MiniMap<'a> {
|
|||||||
SiteKind::Cave => self.imgs.mmap_site_cave_bg,
|
SiteKind::Cave => self.imgs.mmap_site_cave_bg,
|
||||||
SiteKind::Tree => self.imgs.mmap_site_tree,
|
SiteKind::Tree => self.imgs.mmap_site_tree,
|
||||||
SiteKind::Gnarling => self.imgs.mmap_site_gnarling_bg,
|
SiteKind::Gnarling => self.imgs.mmap_site_gnarling_bg,
|
||||||
|
SiteKind::Bridge => self.imgs.mmap_site_bridge_bg,
|
||||||
})
|
})
|
||||||
.x_y_position_relative_to(
|
.x_y_position_relative_to(
|
||||||
state.ids.map_layers[0],
|
state.ids.map_layers[0],
|
||||||
@ -733,6 +735,7 @@ impl<'a> Widget for MiniMap<'a> {
|
|||||||
SiteKind::Cave => self.imgs.mmap_site_cave,
|
SiteKind::Cave => self.imgs.mmap_site_cave,
|
||||||
SiteKind::Tree => self.imgs.mmap_site_tree,
|
SiteKind::Tree => self.imgs.mmap_site_tree,
|
||||||
SiteKind::Gnarling => self.imgs.mmap_site_gnarling,
|
SiteKind::Gnarling => self.imgs.mmap_site_gnarling,
|
||||||
|
SiteKind::Bridge => self.imgs.mmap_site_bridge,
|
||||||
})
|
})
|
||||||
.middle_of(state.ids.mmap_site_icons_bgs[i])
|
.middle_of(state.ids.mmap_site_icons_bgs[i])
|
||||||
.w_h(20.0, 20.0)
|
.w_h(20.0, 20.0)
|
||||||
|
@ -140,6 +140,7 @@ pub enum Interface {
|
|||||||
MapShowTowns(bool),
|
MapShowTowns(bool),
|
||||||
MapShowDungeons(bool),
|
MapShowDungeons(bool),
|
||||||
MapShowCastles(bool),
|
MapShowCastles(bool),
|
||||||
|
MapShowBridges(bool),
|
||||||
MapShowCaves(bool),
|
MapShowCaves(bool),
|
||||||
MapShowTrees(bool),
|
MapShowTrees(bool),
|
||||||
MapShowPeaks(bool),
|
MapShowPeaks(bool),
|
||||||
@ -632,6 +633,9 @@ impl SettingsChange {
|
|||||||
Interface::MapShowCastles(map_show_castles) => {
|
Interface::MapShowCastles(map_show_castles) => {
|
||||||
settings.interface.map_show_castles = 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) => {
|
Interface::MapShowCaves(map_show_caves) => {
|
||||||
settings.interface.map_show_caves = 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_towns: bool,
|
||||||
pub map_show_dungeons: bool,
|
pub map_show_dungeons: bool,
|
||||||
pub map_show_castles: bool,
|
pub map_show_castles: bool,
|
||||||
|
pub map_show_bridges: bool,
|
||||||
pub loading_tips: bool,
|
pub loading_tips: bool,
|
||||||
pub map_show_caves: bool,
|
pub map_show_caves: bool,
|
||||||
pub map_show_trees: bool,
|
pub map_show_trees: bool,
|
||||||
@ -83,6 +84,7 @@ impl Default for InterfaceSettings {
|
|||||||
map_show_towns: true,
|
map_show_towns: true,
|
||||||
map_show_dungeons: true,
|
map_show_dungeons: true,
|
||||||
map_show_castles: false,
|
map_show_castles: false,
|
||||||
|
map_show_bridges: false,
|
||||||
loading_tips: true,
|
loading_tips: true,
|
||||||
map_show_caves: true,
|
map_show_caves: true,
|
||||||
map_show_trees: false,
|
map_show_trees: false,
|
||||||
|
@ -35,7 +35,7 @@ packed_simd = { package = "packed_simd_2", version = "0.3.8", optional = true }
|
|||||||
rayon = "1.5"
|
rayon = "1.5"
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
ron = { version = "0.8", default-features = false }
|
ron = { version = "0.8", default-features = false }
|
||||||
# inline_tweak = "1.0.2"
|
inline_tweak = "1.0.2"
|
||||||
kiddo = "0.2"
|
kiddo = "0.2"
|
||||||
strum = "0.24.0"
|
strum = "0.24.0"
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ fn main() {
|
|||||||
sample_pos(
|
sample_pos(
|
||||||
config,
|
config,
|
||||||
sampler,
|
sampler,
|
||||||
|
index,
|
||||||
samples,
|
samples,
|
||||||
uniform_idx_as_vec2(map_size_lg, posi),
|
uniform_idx_as_vec2(map_size_lg, posi),
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
|
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
|
||||||
site2,
|
site2,
|
||||||
util::{attempt, seed_expan, DHashMap, NEIGHBORS},
|
util::{attempt, seed_expan, DHashMap, NEIGHBORS},
|
||||||
Index, Land,
|
Index, IndexRef, Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
astar::Astar,
|
astar::Astar,
|
||||||
@ -53,6 +53,8 @@ pub struct Civs {
|
|||||||
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
pub track_map: DHashMap<Id<Site>, DHashMap<Id<Site>, Id<Track>>>,
|
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 sites: Store<Site>,
|
||||||
pub caves: Store<CaveInfo>,
|
pub caves: Store<CaveInfo>,
|
||||||
}
|
}
|
||||||
@ -187,6 +189,7 @@ impl Civs {
|
|||||||
SiteKind::GiantTree => (12i32, 8.0),
|
SiteKind::GiantTree => (12i32, 8.0),
|
||||||
SiteKind::Gnarling => (16i32, 10.0),
|
SiteKind::Gnarling => (16i32, 10.0),
|
||||||
SiteKind::Citadel => (16i32, 0.0),
|
SiteKind::Citadel => (16i32, 0.0),
|
||||||
|
SiteKind::Bridge(_, _) => (0, 0.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
|
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 mut rng = ctx.reseed().rng;
|
||||||
let site = index.sites.insert(match &sim_site.kind {
|
let site = index.sites.insert({
|
||||||
SiteKind::Settlement => {
|
let index_ref = IndexRef {
|
||||||
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
colors: &index.colors(),
|
||||||
},
|
features: &index.features(),
|
||||||
SiteKind::Dungeon => WorldSite::dungeon(site2::Site::generate_dungeon(
|
index,
|
||||||
&Land::from_sim(ctx.sim),
|
};
|
||||||
&mut rng,
|
match &sim_site.kind {
|
||||||
wpos,
|
SiteKind::Settlement => {
|
||||||
)),
|
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
||||||
SiteKind::Castle => {
|
},
|
||||||
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
|
SiteKind::Dungeon => WorldSite::dungeon(site2::Site::generate_dungeon(
|
||||||
},
|
&Land::from_sim(ctx.sim),
|
||||||
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city(
|
&mut rng,
|
||||||
&Land::from_sim(ctx.sim),
|
wpos,
|
||||||
&mut rng,
|
)),
|
||||||
wpos,
|
SiteKind::Castle => {
|
||||||
)),
|
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
|
||||||
SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town(
|
},
|
||||||
&Land::from_sim(ctx.sim),
|
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city(
|
||||||
&mut rng,
|
&Land::from_sim(ctx.sim),
|
||||||
wpos,
|
&mut rng,
|
||||||
)),
|
wpos,
|
||||||
SiteKind::SavannahPit => WorldSite::savannah_pit(
|
)),
|
||||||
site2::Site::generate_savannah_pit(&Land::from_sim(ctx.sim), &mut rng, wpos),
|
SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town(
|
||||||
),
|
&Land::from_sim(ctx.sim),
|
||||||
SiteKind::DesertCity => WorldSite::desert_city(site2::Site::generate_desert_city(
|
&mut rng,
|
||||||
&Land::from_sim(ctx.sim),
|
wpos,
|
||||||
&mut rng,
|
)),
|
||||||
wpos,
|
SiteKind::SavannahPit => {
|
||||||
)),
|
WorldSite::savannah_pit(site2::Site::generate_savannah_pit(
|
||||||
SiteKind::Tree => {
|
&Land::from_sim(ctx.sim),
|
||||||
WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng))
|
&mut rng,
|
||||||
},
|
wpos,
|
||||||
SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree(
|
))
|
||||||
&Land::from_sim(ctx.sim),
|
},
|
||||||
&mut rng,
|
SiteKind::DesertCity => WorldSite::desert_city(
|
||||||
wpos,
|
site2::Site::generate_desert_city(&Land::from_sim(ctx.sim), &mut rng, wpos),
|
||||||
)),
|
),
|
||||||
SiteKind::Gnarling => WorldSite::gnarling(site2::Site::generate_gnarling(
|
SiteKind::Tree => {
|
||||||
&Land::from_sim(ctx.sim),
|
WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng))
|
||||||
&mut rng,
|
},
|
||||||
wpos,
|
SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree(
|
||||||
)),
|
&Land::from_sim(ctx.sim),
|
||||||
SiteKind::ChapelSite => WorldSite::chapel_site(site2::Site::generate_chapel_site(
|
&mut rng,
|
||||||
&Land::from_sim(ctx.sim),
|
wpos,
|
||||||
&mut rng,
|
)),
|
||||||
wpos,
|
SiteKind::Gnarling => WorldSite::gnarling(site2::Site::generate_gnarling(
|
||||||
)),
|
&Land::from_sim(ctx.sim),
|
||||||
SiteKind::Citadel => WorldSite::gnarling(site2::Site::generate_citadel(
|
&mut rng,
|
||||||
&Land::from_sim(ctx.sim),
|
wpos,
|
||||||
&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);
|
sim_site.site_tmp = Some(site);
|
||||||
let site_ref = &index.sites[site];
|
let site_ref = &index.sites[site];
|
||||||
@ -980,12 +997,21 @@ impl Civs {
|
|||||||
) -> Id<Site> {
|
) -> Id<Site> {
|
||||||
const SITE_AREA: Range<usize> = 1..4; //64..256;
|
const SITE_AREA: Range<usize> = 1..4; //64..256;
|
||||||
|
|
||||||
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
|
fn establish_site(
|
||||||
Some(place) => place,
|
civs: &mut Civs,
|
||||||
None => self.establish_place(ctx, loc, SITE_AREA),
|
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
|
// Find neighbors
|
||||||
const MAX_NEIGHBOR_DISTANCE: f32 = 2000.0;
|
const MAX_NEIGHBOR_DISTANCE: f32 = 2000.0;
|
||||||
@ -1017,7 +1043,12 @@ impl Civs {
|
|||||||
{
|
{
|
||||||
for (nearby, _) in nearby.into_iter().take(5) {
|
for (nearby, _) in nearby.into_iter().take(5) {
|
||||||
// Find a novel path
|
// 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
|
// Find a path using existing paths
|
||||||
if self
|
if self
|
||||||
.route_between(site, nearby)
|
.route_between(site, nearby)
|
||||||
@ -1027,16 +1058,55 @@ impl Civs {
|
|||||||
{
|
{
|
||||||
// Write the track to the world as a path
|
// Write the track to the world as a path
|
||||||
for locs in path.nodes().windows(3) {
|
for locs in path.nodes().windows(3) {
|
||||||
let to_prev_idx = NEIGHBORS
|
let mut randomize_offset = false;
|
||||||
|
if let Some((i, _)) = NEIGHBORS
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, dir)| **dir == locs[0] - locs[1])
|
.find(|(_, dir)| **dir == locs[0] - locs[1])
|
||||||
.expect("Track locations must be neighbors")
|
{
|
||||||
.0;
|
ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |=
|
||||||
let to_next_idx = 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()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, dir)| **dir == locs[2] - locs[1])
|
.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")
|
.expect("Track locations must be neighbors")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
@ -1047,8 +1117,14 @@ impl Civs {
|
|||||||
let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
|
let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
|
||||||
chunk.path.0.neighbors |=
|
chunk.path.0.neighbors |=
|
||||||
(1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
|
(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
|
// Take note of the track
|
||||||
@ -1125,21 +1201,25 @@ impl Civs {
|
|||||||
/// Attempt to find a path between two locations
|
/// Attempt to find a path between two locations
|
||||||
fn find_path(
|
fn find_path(
|
||||||
ctx: &mut GenCtx<impl Rng>,
|
ctx: &mut GenCtx<impl Rng>,
|
||||||
|
get_bridge: impl Fn(Vec2<i32>) -> Option<Vec2<i32>>,
|
||||||
a: Vec2<i32>,
|
a: Vec2<i32>,
|
||||||
b: Vec2<i32>,
|
b: Vec2<i32>,
|
||||||
) -> Option<(Path<Vec2<i32>>, f32)> {
|
) -> Option<(Path<Vec2<i32>>, f32)> {
|
||||||
const MAX_PATH_ITERS: usize = 100_000;
|
const MAX_PATH_ITERS: usize = 100_000;
|
||||||
let sim = &ctx.sim;
|
let sim = &ctx.sim;
|
||||||
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
||||||
|
let get_bridge = &get_bridge;
|
||||||
let neighbors = |l: &Vec2<i32>| {
|
let neighbors = |l: &Vec2<i32>| {
|
||||||
let l = *l;
|
let l = *l;
|
||||||
NEIGHBORS
|
NEIGHBORS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(move |dir| walk_in_dir(sim, l, **dir).is_some())
|
.filter_map(move |dir| walk_in_dir(sim, get_bridge, l, *dir))
|
||||||
.map(move |dir| 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;
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||||
// We use this hasher (FxHasher64) because
|
// We use this hasher (FxHasher64) because
|
||||||
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
// (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
|
/// 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
|
/// If permitted, the approximate relative const of traversal is given
|
||||||
// (TODO: by whom?)
|
// (TODO: by whom?)
|
||||||
fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
fn walk_in_dir(
|
||||||
if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + 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| (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 a_chunk = sim.get(a)?;
|
||||||
let b_chunk = sim.get(a + dir)?;
|
let b_chunk = sim.get(a + dir)?;
|
||||||
|
|
||||||
let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 5.0).powi(2);
|
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
|
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
|
||||||
} 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 wild_cost = if b_chunk.path.0.is_way() {
|
let wild_cost = if b_chunk.path.0.is_way() {
|
||||||
0.0 // Traversing existing paths has no additional cost!
|
0.0 // Traversing existing paths has no additional cost!
|
||||||
} else {
|
} else {
|
||||||
3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1184,8 +1274,11 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
|||||||
|
|
||||||
/// Return true if a position is suitable for walking on
|
/// Return true if a position is suitable for walking on
|
||||||
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||||
if let Some(chunk) = sim.get(loc) {
|
if sim.get(loc).is_some() {
|
||||||
!chunk.river.is_ocean() && !chunk.river.is_lake() && !chunk.near_cliffs()
|
!NEIGHBORS.iter().any(|n| {
|
||||||
|
sim.get(loc + *n)
|
||||||
|
.map_or(false, |chunk| chunk.river.near_water())
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -1312,6 +1405,7 @@ pub enum SiteKind {
|
|||||||
GiantTree,
|
GiantTree,
|
||||||
Gnarling,
|
Gnarling,
|
||||||
Citadel,
|
Citadel,
|
||||||
|
Bridge(Vec2<i32>, Vec2<i32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteKind {
|
impl SiteKind {
|
||||||
@ -1431,6 +1525,8 @@ impl SiteKind {
|
|||||||
&& has_building_materials
|
&& has_building_materials
|
||||||
&& industry_score > score_threshold
|
&& industry_score > score_threshold
|
||||||
&& warm_or_firewood
|
&& warm_or_firewood
|
||||||
|
// Because of how the algorithm for site2 towns work, they have to start on land.
|
||||||
|
&& on_land()
|
||||||
};
|
};
|
||||||
match self {
|
match self {
|
||||||
SiteKind::Gnarling => {
|
SiteKind::Gnarling => {
|
||||||
@ -1495,6 +1591,7 @@ impl SiteKind {
|
|||||||
},
|
},
|
||||||
SiteKind::Dungeon => on_land(),
|
SiteKind::Dungeon => on_land(),
|
||||||
SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(6.7),
|
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_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) }
|
||||||
|
|
||||||
|
pub fn is_bridge(&self) -> bool { matches!(self.kind, SiteKind::Bridge(_, _)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
@ -1194,7 +1194,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
marble,
|
marble,
|
||||||
marble_mid,
|
marble_mid,
|
||||||
marble_small,
|
marble_small,
|
||||||
rock_density,
|
rock_density: if spawn_rules.trees { rock_density } else { 0.0 },
|
||||||
temp,
|
temp,
|
||||||
humidity,
|
humidity,
|
||||||
spawn_rate,
|
spawn_rate,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::sim;
|
use crate::{column::ColumnGen, sim, util::Sampler, ColumnSample, IndexRef};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -45,4 +45,13 @@ impl<'a> Land<'a> {
|
|||||||
) -> Option<(f32, Vec2<f32>, sim::Path, Vec2<f32>)> {
|
) -> Option<(f32, Vec2<f32>, sim::Path, Vec2<f32>)> {
|
||||||
self.sim.and_then(|sim| sim.get_nearest_path(wpos))
|
self.sim.and_then(|sim| sim.get_nearest_path(wpos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn column_sample<'sample>(
|
||||||
|
&'sample self,
|
||||||
|
wpos: Vec2<i32>,
|
||||||
|
index: IndexRef<'sample>,
|
||||||
|
) -> Option<ColumnSample<'sample>> {
|
||||||
|
self.sim
|
||||||
|
.and_then(|sim| ColumnGen::new(sim).get((wpos, index, None)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,7 @@ impl World {
|
|||||||
civ::SiteKind::Gnarling => world_msg::SiteKind::Gnarling,
|
civ::SiteKind::Gnarling => world_msg::SiteKind::Gnarling,
|
||||||
civ::SiteKind::ChapelSite => world_msg::SiteKind::ChapelSite,
|
civ::SiteKind::ChapelSite => world_msg::SiteKind::ChapelSite,
|
||||||
civ::SiteKind::Citadel => world_msg::SiteKind::Castle,
|
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),
|
wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
column::ColumnSample,
|
column::ColumnSample,
|
||||||
sim::{RiverKind, WorldSim},
|
sim::{RiverKind, WorldSim},
|
||||||
CONFIG,
|
site::SiteKind,
|
||||||
|
IndexRef, CONFIG,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
terrain::{
|
terrain::{
|
||||||
@ -71,6 +72,7 @@ pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2<i32>) -> f
|
|||||||
pub fn sample_pos(
|
pub fn sample_pos(
|
||||||
config: &MapConfig,
|
config: &MapConfig,
|
||||||
sampler: &WorldSim,
|
sampler: &WorldSim,
|
||||||
|
index: IndexRef,
|
||||||
samples: Option<&[Option<ColumnSample>]>,
|
samples: Option<&[Option<ColumnSample>]>,
|
||||||
pos: Vec2<i32>,
|
pos: Vec2<i32>,
|
||||||
) -> MapSample {
|
) -> MapSample {
|
||||||
@ -102,6 +104,7 @@ pub fn sample_pos(
|
|||||||
river_kind,
|
river_kind,
|
||||||
spline_derivative,
|
spline_derivative,
|
||||||
is_path,
|
is_path,
|
||||||
|
is_bridge,
|
||||||
) = sampler
|
) = sampler
|
||||||
.get(pos)
|
.get(pos)
|
||||||
.map(|sample| {
|
.map(|sample| {
|
||||||
@ -116,6 +119,21 @@ pub fn sample_pos(
|
|||||||
sample.river.river_kind,
|
sample.river.river_kind,
|
||||||
sample.river.spline_derivative,
|
sample.river.spline_derivative,
|
||||||
sample.path.0.is_way(),
|
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((
|
.unwrap_or((
|
||||||
@ -129,6 +147,7 @@ pub fn sample_pos(
|
|||||||
None,
|
None,
|
||||||
Vec2::zero(),
|
Vec2::zero(),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
));
|
));
|
||||||
|
|
||||||
let humidity = humidity.clamp(0.0, 1.0);
|
let humidity = humidity.clamp(0.0, 1.0);
|
||||||
@ -246,7 +265,9 @@ pub fn sample_pos(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// TODO: Make principled.
|
// 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)
|
Rgb::new(0x37, 0x29, 0x23)
|
||||||
} else {
|
} else {
|
||||||
rgb
|
rgb
|
||||||
|
@ -1657,7 +1657,7 @@ impl WorldSim {
|
|||||||
map_config.is_shaded = false;
|
map_config.is_shaded = false;
|
||||||
|
|
||||||
map_config.generate(
|
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| sample_wpos(&map_config, self, pos),
|
||||||
|pos, (r, g, b, _a)| {
|
|pos, (r, g, b, _a)| {
|
||||||
// We currently ignore alpha and replace it with the height at pos, scaled to
|
// We currently ignore alpha and replace it with the height at pos, scaled to
|
||||||
|
@ -130,6 +130,7 @@ impl Environment {
|
|||||||
SiteKind::GiantTree(_) => (),
|
SiteKind::GiantTree(_) => (),
|
||||||
SiteKind::Gnarling(_) => {},
|
SiteKind::Gnarling(_) => {},
|
||||||
SiteKind::ChapelSite(_) => {},
|
SiteKind::ChapelSite(_) => {},
|
||||||
|
SiteKind::Bridge(_) => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if towns.valid() {
|
if towns.valid() {
|
||||||
|
@ -73,6 +73,7 @@ pub enum SiteKind {
|
|||||||
ChapelSite(site2::Site),
|
ChapelSite(site2::Site),
|
||||||
GiantTree(site2::Site),
|
GiantTree(site2::Site),
|
||||||
Gnarling(site2::Site),
|
Gnarling(site2::Site),
|
||||||
|
Bridge(site2::Site),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn radius(&self) -> f32 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
SiteKind::Settlement(s) => s.radius(),
|
SiteKind::Settlement(s) => s.radius(),
|
||||||
@ -166,6 +174,7 @@ impl Site {
|
|||||||
SiteKind::Tree(t) => t.radius(),
|
SiteKind::Tree(t) => t.radius(),
|
||||||
SiteKind::GiantTree(gt) => gt.radius(),
|
SiteKind::GiantTree(gt) => gt.radius(),
|
||||||
SiteKind::Gnarling(g) => g.radius(),
|
SiteKind::Gnarling(g) => g.radius(),
|
||||||
|
SiteKind::Bridge(b) => b.radius(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +191,7 @@ impl Site {
|
|||||||
SiteKind::Tree(t) => t.origin,
|
SiteKind::Tree(t) => t.origin,
|
||||||
SiteKind::GiantTree(gt) => gt.origin,
|
SiteKind::GiantTree(gt) => gt.origin,
|
||||||
SiteKind::Gnarling(g) => g.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::Tree(t) => t.spawn_rules(wpos),
|
||||||
SiteKind::GiantTree(gt) => gt.spawn_rules(wpos),
|
SiteKind::GiantTree(gt) => gt.spawn_rules(wpos),
|
||||||
SiteKind::Gnarling(g) => g.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::Tree(_) => "Giant Tree",
|
||||||
SiteKind::GiantTree(gt) => gt.name(),
|
SiteKind::GiantTree(gt) => gt.name(),
|
||||||
SiteKind::Gnarling(g) => g.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::Tree(t) => t.render(canvas, dynamic_rng),
|
||||||
SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng),
|
SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng),
|
||||||
SiteKind::Gnarling(g) => g.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::Tree(_) => {},
|
||||||
SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement),
|
SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement),
|
||||||
SiteKind::Gnarling(g) => g.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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use num::cast::AsPrimitive;
|
use num::cast::AsPrimitive;
|
||||||
use std::{cell::RefCell, sync::Arc};
|
use std::{cell::RefCell, ops::RangeBounds, sync::Arc};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[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
|
- ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32
|
||||||
},
|
},
|
||||||
Primitive::Cylinder(aabb) => {
|
Primitive::Cylinder(aabb) => {
|
||||||
|
// Add 0.5 since the aabb is exclusive.
|
||||||
|
let fpos = pos.as_::<f32>().xy() - aabb.as_::<f32>().center().xy() + 0.5;
|
||||||
|
let size = Vec3::from(aabb.size().as_::<f32>()).xy();
|
||||||
(aabb.min.z..aabb.max.z).contains(&pos.z)
|
(aabb.min.z..aabb.max.z).contains(&pos.z)
|
||||||
&& (pos
|
&& (2.0 * fpos / size).magnitude_squared() <= 1.0
|
||||||
.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)
|
|
||||||
},
|
},
|
||||||
Primitive::Cone(aabb) => {
|
Primitive::Cone(aabb) => {
|
||||||
(aabb.min.z..aabb.max.z).contains(&pos.z)
|
(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))
|
Self::contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub))
|
||||||
},
|
},
|
||||||
Primitive::Scale(prim, vec) => {
|
Primitive::Scale(prim, vec) => {
|
||||||
let center =
|
let center = Self::get_bounds(tree, *prim).as_::<f32>().center();
|
||||||
Self::get_bounds(tree, *prim).center().as_::<f32>() - Vec3::broadcast(0.5);
|
|
||||||
let fpos = pos.as_::<f32>();
|
let fpos = pos.as_::<f32>();
|
||||||
let spos = (center + ((center - fpos) / vec))
|
let spos = (center + ((fpos - center) / vec))
|
||||||
.map(|x| x.round())
|
.map(|x| x.round())
|
||||||
.as_::<i32>();
|
.as_::<i32>();
|
||||||
Self::contains_at(tree, *prim, spos)
|
Self::contains_at(tree, *prim, spos)
|
||||||
@ -782,6 +779,20 @@ impl Painter {
|
|||||||
self.prim(Primitive::Cylinder(aabb.made_valid()))
|
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<i32>, 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
|
/// Returns a `PrimitiveRef` of a cylinder using a radius check where a
|
||||||
/// radius and origin are parameters instead of a bounding box.
|
/// radius and origin are parameters instead of a bounding box.
|
||||||
pub fn cylinder_with_radius(
|
pub fn cylinder_with_radius(
|
||||||
@ -970,11 +981,20 @@ impl Painter {
|
|||||||
/// Returns a `PrimitiveRef` of an Aabb with a slope cut into it. The
|
/// Returns a `PrimitiveRef` of an Aabb with a slope cut into it. The
|
||||||
/// `inset` governs the slope. The `dir` determines which direction the
|
/// `inset` governs the slope. The `dir` determines which direction the
|
||||||
/// ramp points.
|
/// ramp points.
|
||||||
pub fn ramp(&self, aabb: Aabb<i32>, inset: i32, dir: Dir) -> PrimitiveRef {
|
pub fn ramp_inset(&self, aabb: Aabb<i32>, inset: i32, dir: Dir) -> PrimitiveRef {
|
||||||
let aabb = aabb.made_valid();
|
let aabb = aabb.made_valid();
|
||||||
self.prim(Primitive::Ramp { aabb, inset, dir })
|
self.prim(Primitive::Ramp { aabb, inset, dir })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ramp(&self, aabb: Aabb<i32>, 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
|
/// Returns a `PrimitiveRef` of a triangular prism with the base being
|
||||||
/// vertical. A gable is a tent shape. The `inset` governs the slope of
|
/// vertical. A gable is a tent shape. The `inset` governs the slope of
|
||||||
/// the gable. The `dir` determines which way the gable points.
|
/// 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<i32>, 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<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(),
|
||||||
|
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<i32>, range: impl RangeBounds<i32>) -> 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.
|
/// The area that the canvas is currently rendering.
|
||||||
pub fn render_aabr(&self) -> Aabr<i32> { self.render_area }
|
pub fn render_aabr(&self) -> Aabr<i32> { self.render_area }
|
||||||
|
|
||||||
@ -1133,7 +1270,7 @@ pub trait PrimitiveTransform {
|
|||||||
/// Scales the primitive along each axis by the x, y, and z components of
|
/// Scales the primitive along each axis by the x, y, and z components of
|
||||||
/// the `scale` vector respectively.
|
/// the `scale` vector respectively.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn scale(self, scale: Vec3<f32>) -> Self;
|
fn scale(self, scale: Vec3<impl AsPrimitive<f32>>) -> Self;
|
||||||
/// Returns a `PrimitiveRef` of the primitive in addition to the same
|
/// Returns a `PrimitiveRef` of the primitive in addition to the same
|
||||||
/// primitive translated by `offset` and repeated `count` times, each time
|
/// primitive translated by `offset` and repeated `count` times, each time
|
||||||
/// translated by an additional offset.
|
/// translated by an additional offset.
|
||||||
@ -1150,7 +1287,9 @@ impl<'a> PrimitiveTransform for PrimitiveRef<'a> {
|
|||||||
self.painter.prim(Primitive::rotate_about(self, rot, point))
|
self.painter.prim(Primitive::rotate_about(self, rot, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scale(self, scale: Vec3<f32>) -> Self { self.painter.prim(Primitive::scale(self, scale)) }
|
fn scale(self, scale: Vec3<impl AsPrimitive<f32>>) -> Self {
|
||||||
|
self.painter.prim(Primitive::scale(self, scale.as_()))
|
||||||
|
}
|
||||||
|
|
||||||
fn repeat(self, offset: Vec3<i32>, count: u32) -> Self {
|
fn repeat(self, offset: Vec3<i32>, count: u32) -> Self {
|
||||||
self.painter.prim(Primitive::repeat(self, offset, count))
|
self.painter.prim(Primitive::repeat(self, offset, count))
|
||||||
@ -1172,7 +1311,7 @@ impl<'a, const N: usize> PrimitiveTransform for [PrimitiveRef<'a>; N] {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scale(mut self, scale: Vec3<f32>) -> Self {
|
fn scale(mut self, scale: Vec3<impl AsPrimitive<f32>>) -> Self {
|
||||||
for prim in &mut self {
|
for prim in &mut self {
|
||||||
*prim = prim.scale(scale);
|
*prim = prim.scale(scale);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
sim::Path,
|
sim::Path,
|
||||||
site::{namegen::NameGen, SpawnRules},
|
site::{namegen::NameGen, SpawnRules},
|
||||||
util::{attempt, DHashSet, Grid, CARDINALS, SQUARE_4, SQUARE_9},
|
util::{attempt, DHashSet, Grid, CARDINALS, SQUARE_4, SQUARE_9},
|
||||||
Canvas, Land,
|
Canvas, IndexRef, Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
astar::Astar,
|
astar::Astar,
|
||||||
@ -68,7 +68,7 @@ impl Site {
|
|||||||
// 25 Seems to be big enough for the current scale of 4.0
|
// 25 Seems to be big enough for the current scale of 4.0
|
||||||
25
|
25
|
||||||
} else {
|
} else {
|
||||||
1
|
5
|
||||||
})
|
})
|
||||||
* TILE_SIZE as i32) as f32
|
* TILE_SIZE as i32) as f32
|
||||||
}
|
}
|
||||||
@ -988,6 +988,78 @@ impl Site {
|
|||||||
site
|
site
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_bridge(
|
||||||
|
land: &Land,
|
||||||
|
index: IndexRef,
|
||||||
|
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 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<i32>) -> Vec2<i32> {
|
pub fn wpos_tile_pos(&self, wpos2d: Vec2<i32>) -> Vec2<i32> {
|
||||||
(wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))
|
(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)
|
desert_city_temple.render_collect(self, canvas)
|
||||||
},
|
},
|
||||||
PlotKind::Citadel(citadel) => citadel.render_collect(self, canvas),
|
PlotKind::Citadel(citadel) => citadel.render_collect(self, canvas),
|
||||||
|
PlotKind::Bridge(bridge) => bridge.render_collect(self, canvas),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
mod bridge;
|
||||||
mod castle;
|
mod castle;
|
||||||
mod citadel;
|
mod citadel;
|
||||||
mod cliff_tower;
|
mod cliff_tower;
|
||||||
@ -12,7 +13,7 @@ mod sea_chapel;
|
|||||||
mod workshop;
|
mod workshop;
|
||||||
|
|
||||||
pub use self::{
|
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,
|
desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple,
|
||||||
dungeon::Dungeon, giant_tree::GiantTree, gnarling::GnarlingFortification, house::House,
|
dungeon::Dungeon, giant_tree::GiantTree, gnarling::GnarlingFortification, house::House,
|
||||||
savannah_pit::SavannahPit, sea_chapel::SeaChapel, workshop::Workshop,
|
savannah_pit::SavannahPit, sea_chapel::SeaChapel, workshop::Workshop,
|
||||||
@ -66,4 +67,5 @@ pub enum PlotKind {
|
|||||||
CliffTower(CliffTower),
|
CliffTower(CliffTower),
|
||||||
Citadel(Citadel),
|
Citadel(Citadel),
|
||||||
SavannahPit(SavannahPit),
|
SavannahPit(SavannahPit),
|
||||||
|
Bridge(Bridge),
|
||||||
}
|
}
|
||||||
|
956
world/src/site2/plot/bridge.rs
Normal file
956
world/src/site2/plot/bridge.rs
Normal file
@ -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<i32>,
|
||||||
|
start_dist: i32,
|
||||||
|
end: Vec3<i32>,
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.choose(rng)
|
||||||
|
.unwrap_or(BridgeKind::Flat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
BridgeKind::HangBridge => 2,
|
||||||
|
_ => 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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<i32>, 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<i32>, 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<i32>, 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<i32>| {
|
||||||
|
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<i32>,
|
||||||
|
pub(crate) end: Vec3<i32>,
|
||||||
|
pub(crate) dir: Dir,
|
||||||
|
center: Vec3<i32>,
|
||||||
|
kind: BridgeKind,
|
||||||
|
biome: BiomeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bridge {
|
||||||
|
pub fn generate(
|
||||||
|
land: &Land,
|
||||||
|
index: IndexRef,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
site: &Site,
|
||||||
|
start: Vec2<i32>,
|
||||||
|
end: Vec2<i32>,
|
||||||
|
) -> Self {
|
||||||
|
let start = site.tile_wpos(start);
|
||||||
|
let end = site.tile_wpos(end);
|
||||||
|
|
||||||
|
let min_water_dist = 5;
|
||||||
|
let find_edge = |start: Vec2<i32>, end: Vec2<i32>| {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -349,7 +349,7 @@ impl Structure for DesertCityMultiPlot {
|
|||||||
.clear();
|
.clear();
|
||||||
// Stairs for each storey
|
// Stairs for each storey
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec2::new(center.x - room_length, center.y - room_length + 1)
|
min: Vec2::new(center.x - room_length, center.y - room_length + 1)
|
||||||
.with_z(floor_level),
|
.with_z(floor_level),
|
||||||
@ -1016,7 +1016,7 @@ impl Structure for DesertCityMultiPlot {
|
|||||||
.clear();
|
.clear();
|
||||||
// Stairs for each storey
|
// Stairs for each storey
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec2::new(
|
min: Vec2::new(
|
||||||
subplot_center.x - room_length,
|
subplot_center.x - room_length,
|
||||||
@ -1628,7 +1628,6 @@ impl Structure for DesertCityMultiPlot {
|
|||||||
)
|
)
|
||||||
.with_z(base + tower_height + 1),
|
.with_z(base + tower_height + 1),
|
||||||
},
|
},
|
||||||
2,
|
|
||||||
Dir::NegX,
|
Dir::NegX,
|
||||||
)
|
)
|
||||||
.fill(sandstone.clone());
|
.fill(sandstone.clone());
|
||||||
|
@ -241,7 +241,7 @@ impl Structure for SeaChapel {
|
|||||||
})
|
})
|
||||||
.fill(white.clone());
|
.fill(white.clone());
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(
|
min: Vec3::new(
|
||||||
center.x - (diameter / 2) - 12,
|
center.x - (diameter / 2) - 12,
|
||||||
@ -275,7 +275,7 @@ impl Structure for SeaChapel {
|
|||||||
})
|
})
|
||||||
.fill(white.clone());
|
.fill(white.clone());
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(
|
min: Vec3::new(
|
||||||
center.x + (diameter / 2) - 2,
|
center.x + (diameter / 2) - 2,
|
||||||
@ -3059,7 +3059,7 @@ impl Structure for SeaChapel {
|
|||||||
.fill(window_ver.clone());
|
.fill(window_ver.clone());
|
||||||
// chapel main room pulpit stairs1
|
// chapel main room pulpit stairs1
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(center.x - 8, center.y - (diameter / 4) - 2, base - 3),
|
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),
|
max: Vec3::new(center.x - 3, center.y - (diameter / 4) + 7, base + 2),
|
||||||
@ -3070,7 +3070,7 @@ impl Structure for SeaChapel {
|
|||||||
.fill(white.clone());
|
.fill(white.clone());
|
||||||
// chapel main room pulpit stairs2
|
// chapel main room pulpit stairs2
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(center.x + 3, center.y - (diameter / 4) - 2, base - 3),
|
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),
|
max: Vec3::new(center.x + 8, center.y - (diameter / 4) + 7, base + 2),
|
||||||
@ -3081,7 +3081,7 @@ impl Structure for SeaChapel {
|
|||||||
.fill(white.clone());
|
.fill(white.clone());
|
||||||
// chapel main room pulpit stairs2
|
// chapel main room pulpit stairs2
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(center.x - 8, center.y + (diameter / 4) - 7, base - 3),
|
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),
|
max: Vec3::new(center.x - 3, center.y + (diameter / 4) + 2, base + 2),
|
||||||
@ -3092,7 +3092,7 @@ impl Structure for SeaChapel {
|
|||||||
.fill(white.clone());
|
.fill(white.clone());
|
||||||
// chapel main room pulpit stairs4
|
// chapel main room pulpit stairs4
|
||||||
painter
|
painter
|
||||||
.ramp(
|
.ramp_inset(
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::new(center.x + 3, center.y + (diameter / 4) - 7, base - 3),
|
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),
|
max: Vec3::new(center.x + 8, center.y + (diameter / 4) + 2, base + 2),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
|
|
||||||
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -13,6 +15,8 @@ pub enum Dir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dir {
|
impl Dir {
|
||||||
|
pub const ALL: [Dir; 4] = [Dir::X, Dir::Y, Dir::NegX, Dir::NegY];
|
||||||
|
|
||||||
pub fn choose(rng: &mut impl Rng) -> Dir {
|
pub fn choose(rng: &mut impl Rng) -> Dir {
|
||||||
match rng.gen_range(0..4) {
|
match rng.gen_range(0..4) {
|
||||||
0 => Dir::X,
|
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<i32> {
|
pub fn to_vec2(self) -> Vec2<i32> {
|
||||||
match self {
|
match self {
|
||||||
Dir::X => Vec2::new(1, 0),
|
Dir::X => Vec2::new(1, 0),
|
||||||
@ -91,21 +119,21 @@ impl Dir {
|
|||||||
/// use veloren_world::site2::util::Dir;
|
/// use veloren_world::site2::util::Dir;
|
||||||
/// let dir = Dir::X;
|
/// 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;
|
/// 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;
|
/// 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;
|
/// 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<i32> {
|
pub fn to_mat3(self) -> Mat3<i32> {
|
||||||
match self {
|
match self {
|
||||||
Dir::X => Mat3::new(1, 0, 0, 0, 1, 0, 0, 0, 1),
|
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),
|
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<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
|
/// Translates this direction to worldspace as if it was relative to the
|
||||||
/// other direction
|
/// other direction
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -131,6 +170,104 @@ impl Dir {
|
|||||||
|
|
||||||
/// Is this direction parallel to y
|
/// Is this direction parallel to y
|
||||||
pub fn is_y(self) -> bool { matches!(self, Dir::Y | Dir::NegY) }
|
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<Vec2<i32>>) -> 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<Vec2<i32>>, other: impl Into<Vec2<i32>>) -> Vec2<i32> {
|
||||||
|
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<T>(self, aabr: Aabr<T>) -> 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<T>(self, aabr: Aabr<T>, other: impl Into<Vec2<T>>) -> Vec2<T> {
|
||||||
|
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<T>(self, aabr: Aabr<T>, offset: T) -> [Aabr<T>; 2]
|
||||||
|
where
|
||||||
|
T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
|
||||||
|
{
|
||||||
|
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<i32>, offset: i32) -> Aabr<i32> {
|
||||||
|
Aabr {
|
||||||
|
min: aabr.min + self.abs().to_vec2() * offset,
|
||||||
|
max: aabr.max - self.abs().to_vec2() * offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl std::ops::Neg for Dir {
|
||||||
|
Loading…
Reference in New Issue
Block a user