Merge branch 'zesterer/cities' into 'master'

Worldgen improvements

See merge request veloren/veloren!245
This commit is contained in:
Forest Anderson 2019-06-26 20:17:44 +00:00
commit 6127fe61fc
41 changed files with 1292 additions and 374 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -20,11 +20,11 @@ use vek::*;
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this.
const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0;
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// A resource that stores the time of day.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TimeOfDay(f64);
pub struct TimeOfDay(pub f64);
/// A resource that stores the tick (i.e: physics) time.
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]

View File

@ -28,7 +28,7 @@ impl Block {
match self.kind {
0 => None,
1 => Some(0.85),
_ => Some(1.0),
_ => Some(3.0),
}
}
}

View File

@ -7,6 +7,7 @@ use common::{
comp,
msg::ServerMsg,
npc::{get_npc_name, NpcKind},
state::TimeOfDay,
};
use specs::{Builder, Entity as EcsEntity, Join};
use vek::*;
@ -59,37 +60,43 @@ lazy_static! {
"jump",
"{d} {d} {d}",
"/jump <dx> <dy> <dz> : Offset your current position",
handle_jump
handle_jump,
),
ChatCommand::new(
"goto",
"{d} {d} {d}",
"/goto <x> <y> <z> : Teleport to a position",
handle_goto
handle_goto,
),
ChatCommand::new(
"alias",
"{}",
"/alias <name> : Change your alias",
handle_alias
handle_alias,
),
ChatCommand::new(
"tp",
"{}",
"/tp <alias> : Teleport to another player",
handle_tp
handle_tp,
),
ChatCommand::new(
"kill",
"{}",
"/kill : Kill yourself",
handle_kill
handle_kill,
),
ChatCommand::new(
"time",
"{} {s}",
"/time : Set the time of day",
handle_time,
),
ChatCommand::new(
"spawn",
"{} {} {d}",
"/spawn <alignment> <entity> [amount] : Spawn a test entity",
handle_spawn
handle_spawn,
),
ChatCommand::new(
"help", "", "/help: Display this message", handle_help)
@ -143,6 +150,33 @@ fn handle_kill(server: &mut Server, entity: EcsEntity, _args: String, _action: &
.map(|s| s.hp.set_to(0, comp::HealthSource::Suicide));
}
fn handle_time(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
let time = scan_fmt!(&args, action.arg_fmt, String);
server.state.ecs_mut().write_resource::<TimeOfDay>().0 = match time.as_ref().map(|s| s.as_str())
{
Some("day") => 12.0 * 3600.0,
Some("night") => 24.0 * 3600.0,
Some("dawn") => 5.0 * 3600.0,
Some("dusk") => 17.0 * 3600.0,
Some(n) => match n.parse() {
Ok(n) => n,
Err(_) => {
server
.clients
.notify(entity, ServerMsg::Chat(format!("'{}' is not a time!", n)));
return;
}
},
None => {
server.clients.notify(
entity,
ServerMsg::Chat("You must specify a time!".to_string()),
);
return;
}
};
}
fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
let opt_alias = scan_fmt!(&args, action.arg_fmt, String);
match opt_alias {

View File

@ -32,13 +32,8 @@ void main() {
vec4(f_norm, 0.0)
).xyz;
float ambient = 0.5;
vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1));
float sun_diffuse = dot(sun_dir, world_norm) * 0.5;
vec3 surf_color = model_col.rgb * f_col * (ambient + sun_diffuse);
float light = get_sun_diffuse(world_norm, time_of_day.x);
vec3 surf_color = model_col.rgb * f_col * 2.0 * light;
float fog_level = fog(f_pos.xy, focus_pos.xy);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x);

View File

@ -26,42 +26,184 @@ float noise(vec3 p){
return o4.y * d.y + o4.x * (1.0 - d.y);
}
vec3 get_sky_color(vec3 dir, float time_of_day) {
bool objects = true;
vec3 get_sun_dir(float time_of_day) {
const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0);
float sun_angle_rad = time_of_day * TIME_FACTOR;
vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad));
return sun_dir;
}
float get_sun_brightness(vec3 sun_dir) {
return max(-sun_dir.z + 0.6, 0.0) * 0.8;
}
const float PERSISTENT_AMBIANCE = 0.015;
float get_sun_diffuse(vec3 norm, float time_of_day) {
const float SUN_AMBIANCE = 0.2;
vec3 sun_dir = get_sun_dir(time_of_day);
float sun_light = get_sun_brightness(sun_dir);
return (SUN_AMBIANCE + max(dot(-norm, sun_dir), 0.0)) * sun_light + PERSISTENT_AMBIANCE;
}
vec3 rand_offs(vec3 pos) {
return sin(pos * vec3(1473.7 * pos.z + 472.3, 8891.1 * pos.x + 723.1, 3813.3 * pos.y + 982.5));
}
vec3 get_sky_color(vec3 dir, float time_of_day) {
// Stars
float star_scale = 30.0;
float star = 0.0;
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 2; j ++) {
for (int k = 0; k < 2; k ++) {
// Star positions
vec3 pos = (floor(dir * star_scale) + vec3(i, j, k) - vec3(0.5)) / star_scale;
// Noisy offsets
pos += 3.0 * rand_offs(pos) / star_scale;
// Find distance to fragment
float dist = length(normalize(pos) - dir);
// Star threshold
if (dist < 0.0015) {
star = 1.0;
}
}
}
}
// Sky color
const vec3 SKY_DAY_TOP = vec3(0.2, 0.3, 0.9);
const vec3 SKY_DAY_MID = vec3(0.1, 0.15, 0.7);
const vec3 SKY_DAY_BOT = vec3(0.025, 0.15, 0.35);
const vec3 SKY_DUSK_TOP = vec3(0.1, 0.15, 0.3);
const vec3 SKY_DUSK_MID = vec3(0.9, 0.3, 0.2);
const vec3 SKY_DUSK_BOT = vec3(0.01, 0.05, 0.15);
const vec3 SKY_NIGHT_TOP = vec3(0.002, 0.002, 0.005);
const vec3 SKY_NIGHT_MID = vec3(0.002, 0.01, 0.03);
const vec3 SKY_NIGHT_BOT = vec3(0.002, 0.002, 0.005);
vec3 sun_dir = get_sun_dir(time_of_day);
vec3 sky_top = mix(
mix(
SKY_DUSK_TOP,
SKY_NIGHT_TOP,
clamp(sun_dir.z, 0, 1)
) + star,
SKY_DAY_TOP,
clamp(-sun_dir.z, 0, 1)
);
vec3 sky_mid = mix(
mix(
SKY_DUSK_MID,
SKY_NIGHT_MID,
clamp(sun_dir.z, 0, 1)
),
SKY_DAY_MID,
clamp(-sun_dir.z, 0, 1)
);
vec3 sky_bot = mix(
mix(
SKY_DUSK_BOT,
SKY_NIGHT_BOT,
clamp(sun_dir.z, 0, 1)
),
SKY_DAY_MID,
clamp(-sun_dir.z, 0, 1)
);
vec3 sky_color = mix(
mix(
sky_mid,
sky_bot,
pow(clamp(-dir.z, 0, 1), 0.4)
),
sky_top,
pow(clamp(dir.z, 0, 1), 1.0)
);
// Sun
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.35, 0.1) * 0.3;
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.9, 0.35) * 200.0;
vec3 sun_halo = pow(max(dot(dir, -sun_dir) + 0.1, 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = pow(max(dot(dir, -sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_light = (sun_halo + sun_surf) * clamp(dir.z * 10.0, 0, 1);
return sky_color + sun_light;
/*
bool objects = true;
vec2 pos2d = dir.xy / dir.z;
const vec3 SKY_TOP = vec3(0.2, 0.3, 0.9);
const vec3 SKY_MIDDLE = vec3(0.1, 0.15, 0.7);
const vec3 SKY_BOTTOM = vec3(0.025, 0.15, 0.35);
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.7, 0.5) * 0.5;
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.4, 0.3) * 0.5;
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.9, 0.35) * 200.0;
float sun_angle_rad = time_of_day * TIME_FACTOR;
vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad));
vec3 sun_dir = get_sun_dir(time_of_day);
float sky_brightness = get_sun_brightness(sun_dir);
vec3 sun_halo = pow(max(dot(dir, sun_dir), 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = pow(max(dot(dir, sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_halo = pow(max(dot(dir, -sun_dir) + 0.1, 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = pow(max(dot(dir, -sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_light = sun_halo + sun_surf;
vec3 sky_top;
float brightess = (sky_brightness + PERSISTENT_AMBIANCE);
vec3 sky_top = SKY_TOP * brightess;
vec3 sky_middle = SKY_MIDDLE * brightess;
if (objects) {
vec3 p = vec3(pos2d + time_of_day * 0.0002, time_of_day * 0.0001);
sky_top = mix(SKY_TOP, vec3(1), pow(noise(p) * 0.8 + noise(p * 3.0) * 0.2, 2.5) * 3.0);
} else {
sky_top = SKY_TOP;
// Clouds
// vec3 p = vec3(pos2d + time_of_day * 0.0002, time_of_day * 0.00003);
// sky_top = mix(sky_top, vec3(1) * brightess, pow(noise(p) * 0.8 + noise(p * 3.0) * 0.2, 2.5) * 3.0);
}
vec3 sky_color = mix(mix(SKY_MIDDLE, sky_top, clamp(dir.z, 0, 1)), SKY_BOTTOM, clamp(-dir.z * 5.0, 0, 1));
if (objects) {
sky_color += sun_light;
sky_top += sun_light;
sky_middle += sun_light;
}
vec3 sky_color = mix(
mix(
sky_middle,
sky_top,
clamp(dir.z * 1.0, 0, 1)
),
SKY_BOTTOM * brightess,
clamp(-dir.z * 3.0, 0, 1)
);
return sky_color;
*/
}
float fog(vec2 f_pos, vec2 focus_pos) {

View File

@ -164,6 +164,7 @@ void main() {
vec2 uv = (f_pos + 1.0) * 0.5;
vec4 fxaa_color = fxaa_apply(src_color, uv * screen_res.xy, screen_res.xy);
//vec4 fxaa_color = texture(src_color, uv);
vec4 hsva_color = vec4(rgb2hsv(fxaa_color.rgb), fxaa_color.a);
hsva_color.y *= 1.3;

View File

@ -14,6 +14,7 @@ out vec3 f_pos;
void main() {
f_pos = v_pos;
// TODO: Make this position-independent to avoid rounding error jittering
gl_Position =
proj_mat *
view_mat *

View File

@ -28,19 +28,7 @@ void main() {
f_norm = vec3(0.0, 0.0, 1.0) * norm_dir;
}
float glob_ambience = 0.0005;
float sun_ambience = 0.8;
vec3 sun_dir = normalize(vec3(0.7, 1.3, 2.1));
float sun_diffuse = dot(sun_dir, f_norm);
float sun_light = sun_ambience + sun_diffuse;
float static_light = glob_ambience + min(sun_light, f_light);
vec3 light = vec3(static_light);
float light = get_sun_diffuse(f_norm, time_of_day.x) * f_light;
vec3 surf_color = f_col * light;
float fog_level = fog(f_pos.xy, focus_pos.xy);

View File

@ -1,10 +1,13 @@
use conrod_core::{
color,
widget::{self, Button, Image, Rectangle},
widget_ids, Positionable, Sizeable, Widget, WidgetCommon,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
use super::{img_ids::Imgs, Fonts};
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR_2};
use client::{self, Client};
use std::{cell::RefCell, rc::Rc};
widget_ids! {
struct Ids {
@ -17,22 +20,28 @@ widget_ids! {
map_frame_r,
map_frame_bl,
map_frame_br,
location_name,
}
}
#[derive(WidgetCommon)]
pub struct Map<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> Map<'a> {
pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(show: &'a Show, client: &'a Client, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
Self {
show,
imgs,
client,
fonts,
common: widget::CommonBuilder::default(),
}
@ -108,6 +117,21 @@ impl<'a> Widget for Map<'a> {
return Some(Event::Close);
}
// Location Name
match self.client.current_chunk() {
Some(chunk) => Text::new(chunk.meta().name())
.mid_top_with_margin_on(state.ids.map_bg, 40.0)
.font_size(40)
.color(TEXT_COLOR_2)
.parent(state.ids.map_frame_r)
.set(state.ids.location_name, ui),
None => Text::new(" ")
.mid_top_with_margin_on(state.ids.map_bg, 3.0)
.font_size(40)
.color(TEXT_COLOR_2)
.set(state.ids.location_name, ui),
}
None
}
}

View File

@ -5,6 +5,9 @@ use conrod_core::{
};
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR};
use client::{self, Client};
use std::{cell::RefCell, rc::Rc};
widget_ids! {
struct Ids {
@ -19,6 +22,8 @@ widget_ids! {
pub struct MiniMap<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a Fonts,
@ -27,9 +32,10 @@ pub struct MiniMap<'a> {
}
impl<'a> MiniMap<'a> {
pub fn new(show: &'a Show, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(show: &'a Show, client: &'a Client, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
Self {
show,
client,
imgs,
fonts,
common: widget::CommonBuilder::default(),
@ -103,12 +109,18 @@ impl<'a> Widget for MiniMap<'a> {
}
// Title
// TODO: Make it display the actual location.
Text::new("Uncanny Valley")
.mid_top_with_margin_on(state.ids.mmap_frame, 3.0)
.font_size(14)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui);
match self.client.current_chunk() {
Some(chunk) => Text::new(chunk.meta().name())
.mid_top_with_margin_on(state.ids.mmap_frame, 3.0)
.font_size(14)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui),
None => Text::new(" ")
.mid_top_with_margin_on(state.ids.mmap_frame, 3.0)
.font_size(14)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui),
}
None
}

View File

@ -38,10 +38,12 @@ use conrod_core::{
};
use specs::Join;
use std::collections::VecDeque;
use std::{cell::RefCell, rc::Rc};
use vek::*;
const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0);
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const TEXT_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0);
const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0);
@ -522,7 +524,9 @@ impl Hud {
}
// MiniMap
match MiniMap::new(&self.show, &self.imgs, &self.fonts).set(self.ids.minimap, ui_widgets) {
match MiniMap::new(&self.show, client, &self.imgs, &self.fonts)
.set(self.ids.minimap, ui_widgets)
{
Some(minimap::Event::Toggle) => self.show.toggle_mini_map(),
None => {}
}
@ -638,7 +642,9 @@ impl Hud {
// Map
if self.show.map {
match Map::new(&self.imgs, &self.fonts).set(self.ids.map, ui_widgets) {
match Map::new(&self.show, client, &self.imgs, &self.fonts)
.set(self.ids.map, ui_widgets)
{
Some(map::Event::Close) => {
self.show.map(false);
self.force_ungrab = true;
@ -647,7 +653,6 @@ impl Hud {
}
}
// Esc-menu
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts).set(self.ids.esc_menu, ui_widgets) {
Some(esc_menu::Event::OpenSettings(tab)) => {

View File

@ -65,7 +65,7 @@ impl Scene {
backdrop_model: renderer
.create_model(&FigureModelCache::load_mesh(
"fixture/selection_bg.vox",
Vec3::new(-55.0, -50.0, -1.0),
Vec3::new(-55.0, -49.5, -2.0),
))
.unwrap(),
backdrop_state: FigureState::new(renderer, FixtureSkeleton::new()),
@ -84,7 +84,7 @@ impl Scene {
.set_orientation(Vec3::new(client.state().get_time() as f32 * 0.0, 0.0, 0.0));
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client);
const CHAR_SELECT_TIME_OF_DAY: f32 = 80000.0; // 12*3600 seconds
if let Err(err) = renderer.update_consts(
&mut self.globals,
&[Globals::new(
@ -92,8 +92,8 @@ impl Scene {
proj_mat,
cam_pos,
self.camera.get_focus_pos(),
100.0,
client.state().get_time_of_day(),
CHAR_SELECT_TIME_OF_DAY,
55800.0,
client.state().get_time(),
renderer.get_resolution(),
)],

View File

@ -142,7 +142,6 @@ widget_ids! {
image_ids! {
struct Imgs {
<VoxelGraphic>
v_logo: "voxygen/element/v_logo.vox",
button: "voxygen/element/buttons/button.vox",
button_hover: "voxygen/element/buttons/button_hover.vox",
button_press: "voxygen/element/buttons/button_press.vox",
@ -171,7 +170,6 @@ image_ids! {
skin_eyes_window: "voxygen/element/frames/skin_eyes.png",
hair_window: "voxygen/element/frames/skin_eyes.png",
accessories_window: "voxygen/element/frames/skin_eyes.png",
color_picker_bg: "voxygen/element/misc_bg/color_picker_blank.png",
slider_range: "voxygen/element/slider/track.png",
slider_indicator: "voxygen/element/slider/indicator.png",
window_frame_2: "voxygen/element/frames/window_2.png",

View File

@ -70,7 +70,9 @@ image_ids! {
<ImageGraphic>
bg: "voxygen/background/bg_main.png",
error_frame: "voxygen/element/frames/window_2.png",
nothing: "voxygen/element/nothing.png",
}
}
font_ids! {
@ -137,6 +139,7 @@ impl MainMenuUi {
let ref mut ui_widgets = self.ui.set_widgets();
let version = env!("CARGO_PKG_VERSION");
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2);
// Background image, Veloren logo, Alpha-Version Label
Image::new(self.imgs.bg)
.middle_of(ui_widgets.window)
@ -302,8 +305,8 @@ impl MainMenuUi {
}
if self.show_servers {
Image::new(self.imgs.error_frame)
.top_left_with_margins_on(ui_widgets.window, 3.0, 3.0)
.w_h(400.0, 400.0)
.mid_top_with_margin_on(self.ids.username_bg, -320.0)
.w_h(400.0, 300.0)
.set(self.ids.servers_frame, ui_widgets);
let net_settings = &global_state.settings.networking;
@ -320,7 +323,7 @@ impl MainMenuUi {
while let Some(item) = items.next(ui_widgets) {
let mut text = "".to_string();
if &net_settings.servers[item.i] == &self.server_address {
text.push_str("* ")
text.push_str("-> ")
} else {
text.push_str(" ")
}
@ -328,11 +331,11 @@ impl MainMenuUi {
if item
.set(
Button::image(self.imgs.button)
.w_h(100.0, 53.0)
.mid_bottom_with_margin_on(self.ids.servers_frame, 5.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
Button::image(self.imgs.nothing)
.w_h(100.0, 50.0)
.mid_top_with_margin_on(self.ids.servers_frame, 10.0)
//.hover_image(self.imgs.button_hover)
//.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label(&text)
.label_font_size(20)
@ -465,10 +468,10 @@ impl MainMenuUi {
if Button::image(self.imgs.button)
.w_h(190.0, 40.0)
.up_from(self.ids.quit_button, 8.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
//.hover_image(self.imgs.button_hover)
//.press_image(self.imgs.button_press)
.label("Settings")
.label_color(TEXT_COLOR)
.label_color(TEXT_COLOR_2)
.label_font_size(20)
.label_y(Relative::Scalar(3.0))
.set(self.ids.settings_button, ui_widgets)

View File

@ -10,10 +10,6 @@ use vek::*;
type FigureVertex = <FigurePipeline as render::Pipeline>::Vertex;
fn create_vertex(origin: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>) -> FigureVertex {
FigureVertex::new(origin, norm, col, 0)
}
impl Meshable for Segment {
type Pipeline = FigurePipeline;
type Supplement = Vec3<f32>;
@ -31,8 +27,11 @@ impl Meshable for Segment {
pos,
offs + pos.map(|e| e as f32),
col,
create_vertex,
|origin, norm, col, ao, light| {
FigureVertex::new(origin, norm, col * ao * light, 0)
},
true,
&[[[1.0; 3]; 3]; 3],
);
}
}

View File

@ -51,7 +51,7 @@ impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone> Meshable for
for x in range.min.x + 1..range.max.x - 1 {
for y in range.min.y + 1..range.max.y - 1 {
let mut neighbour_light = [[(1.0f32, 0.0); 3]; 3];
let mut neighbour_light = [[[1.0f32; 3]; 3]; 3];
for z in (range.min.z..range.max.z).rev() {
let pos = Vec3::new(x, y, z);
@ -60,10 +60,12 @@ impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone> Meshable for
if let Some(col) = self.get(pos).ok().and_then(|vox| vox.get_color()) {
let avg_light = neighbour_light
.iter()
.map(|row| row.iter())
.flatten()
.map(|col| col.iter())
.flatten()
.fold(0.0, |a, (x, _)| a + x)
/ 9.0;
.fold(0.0, |a, x| a + x)
/ 27.0;
let light = avg_light;
let col = col.map(|e| e as f32 / 255.0);
@ -77,30 +79,47 @@ impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone> Meshable for
pos,
offs,
col,
|pos, norm, col| TerrainVertex::new(pos, norm, col, light),
|pos, norm, col, ao, light| {
TerrainVertex::new(
pos,
norm,
Lerp::lerp(Rgb::zero(), col, 1.0),
light * ao,
)
},
false,
&neighbour_light,
);
}
// Shift lighting
neighbour_light[2] = neighbour_light[1];
neighbour_light[1] = neighbour_light[0];
// Accumulate shade under opaque blocks
for i in 0..3 {
for j in 0..3 {
let max_opacity = neighbour_light[i][j].1;
neighbour_light[i][j] = if let Some(opacity) = self
.get(pos + Vec3::new(i as i32 - 1, j as i32 - 1, 0))
neighbour_light[0][i][j] = if let Some(opacity) = self
.get(pos + Vec3::new(i as i32 - 1, j as i32 - 1, -1))
.ok()
.and_then(|vox| vox.get_opacity())
{
(
(neighbour_light[i][j].0 * (1.0 - max_opacity * 0.3))
.max(1.0 - max_opacity * 0.999),
max_opacity.max(opacity),
)
(neighbour_light[0][i][j] * (1.0 - opacity * 0.1))
.max(1.0 - opacity)
} else {
((neighbour_light[i][j].0 * 1.02).min(1.0), max_opacity)
(neighbour_light[0][i][j] * 1.025).min(1.0)
};
}
}
// Spread light
neighbour_light[0] = [[neighbour_light[0]
.iter()
.map(|col| col.iter())
.flatten()
.copied()
.fold(0.0, |a, x| a + x)
/ 9.0; 3]; 3];
}
}
}

View File

@ -10,72 +10,87 @@ use crate::render::{
/// Given volume, position, and cardinal directions, compute each vertex's AO value.
/// `dirs` should be a slice of length 5 so that the sliding window of size 2 over the slice
/// yields each vertex' adjacent positions.
fn get_ao_quad<V: ReadVol>(vol: &V, pos: Vec3<i32>, dirs: &[Vec3<i32>]) -> Vec4<f32> {
fn get_ao_quad<V: ReadVol>(
vol: &V,
pos: Vec3<i32>,
shift: Vec3<i32>,
dirs: &[Vec3<i32>],
corners: &[[usize; 3]; 4],
darknesses: &[[[f32; 3]; 3]; 3],
) -> Vec4<(f32, f32)> {
dirs.windows(2)
.map(|offs| {
.enumerate()
.map(|(i, offs)| {
let (s1, s2) = (
vol.get(pos + offs[0])
vol.get(pos + shift + offs[0])
.map(|v| !v.is_empty())
.unwrap_or(false),
vol.get(pos + offs[1])
vol.get(pos + shift + offs[1])
.map(|v| !v.is_empty())
.unwrap_or(false),
);
if s1 && s2 {
0.0
} else {
let corner = vol
.get(pos + offs[0] + offs[1])
.map(|v| !v.is_empty())
.unwrap_or(false);
// Map both 1 and 2 neighbors to 0.5 occlusion.
if s1 || s2 || corner {
0.5
let darkness = darknesses[corners[i][0]][corners[i][1]][corners[i][2]];
let darkness = darknesses
.iter()
.map(|x| x.iter().map(|y| y.iter()))
.flatten()
.flatten()
.fold(0.0, |a: f32, x| a.max(*x));
(
darkness,
if s1 && s2 {
0.0
} else {
1.0
}
}
let corner = vol
.get(pos + shift + offs[0] + offs[1])
.map(|v| !v.is_empty())
.unwrap_or(false);
// Map both 1 and 2 neighbors to 0.5 occlusion.
if s1 || s2 || corner {
0.5
} else {
1.0
}
},
)
})
.collect::<Vec4<f32>>()
.collect::<Vec4<(f32, f32)>>()
}
// Utility function
fn create_quad<P: Pipeline, F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>) -> P::Vertex>(
fn create_quad<P: Pipeline, F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>, f32, f32) -> P::Vertex>(
origin: Vec3<f32>,
unit_x: Vec3<f32>,
unit_y: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
ao: Vec4<f32>,
darkness_ao: Vec4<(f32, f32)>,
vcons: &F,
) -> Quad<P> {
let ao_scale = 0.95;
let dark = col * (1.0 - ao_scale);
let ao_map = ao.map(|e| 0.15 + e.powf(2.0) * 0.85);
let darkness = darkness_ao.map(|e| e.0);
let ao = darkness_ao.map(|e| e.1);
let ao_map = ao.map(|e| 0.05 + e.powf(1.6) * 0.95);
if ao[0].min(ao[2]) < ao[1].min(ao[3]) {
Quad::new(
vcons(origin + unit_y, norm, Rgb::lerp(dark, col, ao_map[3])),
vcons(origin, norm, Rgb::lerp(dark, col, ao_map[0])),
vcons(origin + unit_x, norm, Rgb::lerp(dark, col, ao_map[1])),
vcons(
origin + unit_x + unit_y,
norm,
Rgb::lerp(dark, col, ao_map[2]),
),
vcons(origin + unit_y, norm, col, darkness[3], ao_map[3]),
vcons(origin, norm, col, darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, col, darkness[1], ao_map[1]),
vcons(origin + unit_x + unit_y, norm, col, darkness[2], ao_map[2]),
)
} else {
Quad::new(
vcons(origin, norm, Rgb::lerp(dark, col, ao_map[0])),
vcons(origin + unit_x, norm, Rgb::lerp(dark, col, ao_map[1])),
vcons(
origin + unit_x + unit_y,
norm,
Rgb::lerp(dark, col, ao_map[2]),
),
vcons(origin + unit_y, norm, Rgb::lerp(dark, col, ao_map[3])),
vcons(origin, norm, col, darkness[0], ao_map[0]),
vcons(origin + unit_x, norm, col, darkness[1], ao_map[1]),
vcons(origin + unit_x + unit_y, norm, col, darkness[2], ao_map[2]),
vcons(origin + unit_y, norm, col, darkness[3], ao_map[3]),
)
}
}
@ -83,7 +98,7 @@ fn create_quad<P: Pipeline, F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>) -> P::Vertex>(
pub fn push_vox_verts<
V: ReadVol,
P: Pipeline,
F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>) -> P::Vertex,
F: Fn(Vec3<f32>, Vec3<f32>, Rgb<f32>, f32, f32) -> P::Vertex,
>(
mesh: &mut Mesh<P>,
vol: &V,
@ -92,6 +107,7 @@ pub fn push_vox_verts<
col: Rgb<f32>,
vcons: F,
error_makes_face: bool,
darknesses: &[[[f32; 3]; 3]; 3],
) {
let (x, y, z) = (Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z());
@ -107,7 +123,14 @@ pub fn push_vox_verts<
Vec3::unit_y(),
-Vec3::unit_x(),
col,
get_ao_quad(vol, pos - Vec3::unit_x(), &[-z, -y, z, y, -z]),
get_ao_quad(
vol,
pos,
-Vec3::unit_x(),
&[-z, -y, z, y, -z],
&[[0; 3]; 4],
darknesses,
),
&vcons,
));
}
@ -123,7 +146,14 @@ pub fn push_vox_verts<
Vec3::unit_z(),
Vec3::unit_x(),
col,
get_ao_quad(vol, pos + Vec3::unit_x(), &[-y, -z, y, z, -y]),
get_ao_quad(
vol,
pos,
Vec3::unit_x(),
&[-y, -z, y, z, -y],
&[[0; 3]; 4],
darknesses,
),
&vcons,
));
}
@ -139,7 +169,14 @@ pub fn push_vox_verts<
Vec3::unit_z(),
-Vec3::unit_y(),
col,
get_ao_quad(vol, pos - Vec3::unit_y(), &[-x, -z, x, z, -x]),
get_ao_quad(
vol,
pos,
-Vec3::unit_y(),
&[-x, -z, x, z, -x],
&[[0; 3]; 4],
darknesses,
),
&vcons,
));
}
@ -155,7 +192,14 @@ pub fn push_vox_verts<
Vec3::unit_x(),
Vec3::unit_y(),
col,
get_ao_quad(vol, pos + Vec3::unit_y(), &[-z, -x, z, x, -z]),
get_ao_quad(
vol,
pos,
Vec3::unit_y(),
&[-z, -x, z, x, -z],
&[[0; 3]; 4],
darknesses,
),
&vcons,
));
}
@ -171,7 +215,14 @@ pub fn push_vox_verts<
Vec3::unit_x(),
-Vec3::unit_z(),
col,
get_ao_quad(vol, pos - Vec3::unit_z(), &[-y, -x, y, x, -y]),
get_ao_quad(
vol,
pos,
-Vec3::unit_z(),
&[-y, -x, y, x, -y],
&[[0; 3]; 4],
darknesses,
),
&vcons,
));
}
@ -187,7 +238,14 @@ pub fn push_vox_verts<
Vec3::unit_y(),
Vec3::unit_z(),
col,
get_ao_quad(vol, pos + Vec3::unit_z(), &[-x, -y, x, y, -x]),
get_ao_quad(
vol,
pos,
Vec3::unit_z(),
&[-x, -y, x, y, -x],
&[[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]],
darknesses,
),
&vcons,
));
}

View File

@ -757,8 +757,8 @@ impl<S: Skeleton> FigureState<S> {
dt: f32,
) {
// Update interpolate pos
self.pos = Lerp::lerp(self.pos, pos, (0.3f32).powf(1.0 / 60.0).powf(1.0 / dt));
self.ori = Slerp::slerp(self.ori, ori, (0.15f32).powf(1.0 / 60.0).powf(1.0 / dt));
self.pos = Lerp::lerp(self.pos, pos, (0.4f32).powf(60.0).powf(dt));
self.ori = Slerp::slerp(self.ori, ori, (0.2f32).powf(60.0).powf(dt));
let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(self.pos)

View File

@ -57,14 +57,6 @@ impl SessionState {
}
}
// TODO: Get rid of this when we're done with it (we're not yet)
/*
match self.client.borrow().current_chunk() {
Some(chunk) => println!("Chunk location: {:?}", chunk.meta().name()),
None => {}
}
*/
Ok(())
}

35
world/examples/city.rs Normal file
View File

@ -0,0 +1,35 @@
use rand::thread_rng;
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::sim::Settlement;
const W: usize = 640;
const H: usize = 480;
fn main() {
let mut win =
minifb::Window::new("City Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let settlement = Settlement::generate(&mut thread_rng());
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = Vec2::new(i as f32, j as f32) * 0.002;
let seed = settlement.get_at(pos).map(|b| b.seed).unwrap_or(0);
buf[j * W + i] = u32::from_le_bytes([
(seed >> 0) as u8,
(seed >> 8) as u8,
(seed >> 16) as u8,
(seed >> 24) as u8,
]);
}
}
win.update_with_buffer(&buf).unwrap();
}
}

42
world/examples/turb.rs Normal file
View File

@ -0,0 +1,42 @@
use noise::{NoiseFn, Seedable, SuperSimplex};
use rand::thread_rng;
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::sim::Settlement;
const W: usize = 640;
const H: usize = 640;
fn main() {
let mut win = minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap();
let nz_x = SuperSimplex::new().set_seed(0);
let nz_y = SuperSimplex::new().set_seed(1);
let mut time = 0.0f64;
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = Vec2::new(i as f64 / W as f64, j as f64 / H as f64) * 0.5 - 0.25;
let pos = pos * 10.0;
let pos = (0..10).fold(pos, |pos, _| pos.map(|e| e.powf(3.0) - 1.0));
let val = if pos.map(|e| e.abs() < 0.5).reduce_and() {
1.0f32
} else {
0.0
};
buf[j * W + i] = u32::from_le_bytes([(val.max(0.0).min(1.0) * 255.0) as u8; 4]);
}
}
win.update_with_buffer(&buf).unwrap();
time += 1.0 / 60.0;
}
}

View File

@ -15,35 +15,45 @@ fn main() {
let mut focus = Vec2::zero();
let mut gain = 1.0;
let mut scale = 4;
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = focus + Vec2::new(i as i32, j as i32) * 4;
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let alt = sampler
let (alt, location) = sampler
.get(pos)
.map(|sample| sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8)
.unwrap_or(0);
.map(|sample| {
(
sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8,
sample.location,
)
})
.unwrap_or((0, None));
buf[j * W + i] = u32::from_le_bytes([alt; 4]);
let loc_color = location
.map(|l| (l.loc_idx as u8 * 17, l.loc_idx as u8 * 13))
.unwrap_or((0, 0));
buf[j * W + i] = u32::from_le_bytes([loc_color.0, loc_color.1, alt, alt]);
}
}
let spd = 32;
if win.is_key_down(minifb::Key::W) {
focus.y -= spd;
focus.y -= spd * scale;
}
if win.is_key_down(minifb::Key::A) {
focus.x -= spd;
focus.x -= spd * scale;
}
if win.is_key_down(minifb::Key::S) {
focus.y += spd;
focus.y += spd * scale;
}
if win.is_key_down(minifb::Key::D) {
focus.x += spd;
focus.x += spd * scale;
}
if win.is_key_down(minifb::Key::Q) {
gain += 10.0;
@ -51,6 +61,12 @@ fn main() {
if win.is_key_down(minifb::Key::E) {
gain -= 10.0;
}
if win.is_key_down(minifb::Key::R) {
scale += 6;
}
if win.is_key_down(minifb::Key::F) {
scale -= 6;
}
win.update_with_buffer(&buf).unwrap();
}

View File

@ -15,7 +15,7 @@ use vek::*;
pub struct BlockGen<'a> {
world: &'a World,
column_cache: HashCache<Vec2<i32>, Option<ColumnSample>>,
column_cache: HashCache<Vec2<i32>, Option<ColumnSample<'a>>>,
column_gen: ColumnGen<'a>,
}
@ -23,17 +23,57 @@ impl<'a> BlockGen<'a> {
pub fn new(world: &'a World, column_gen: ColumnGen<'a>) -> Self {
Self {
world,
column_cache: HashCache::with_capacity(1024),
column_cache: HashCache::with_capacity(64),
column_gen,
}
}
fn sample_column(&mut self, wpos: Vec2<i32>) -> Option<ColumnSample> {
let column_gen = &mut self.column_gen;
self.column_cache
fn sample_column(
column_gen: &ColumnGen<'a>,
cache: &mut HashCache<Vec2<i32>, Option<ColumnSample<'a>>>,
wpos: Vec2<i32>,
) -> Option<ColumnSample<'a>> {
cache
.get(Vec2::from(wpos), |wpos| column_gen.get(wpos))
.clone()
}
fn get_cliff_height(
column_gen: &ColumnGen<'a>,
cache: &mut HashCache<Vec2<i32>, Option<ColumnSample<'a>>>,
wpos: Vec2<f32>,
close_cliffs: &[(Vec2<i32>, u32); 9],
cliff_hill: f32,
) -> f32 {
close_cliffs.iter().fold(
0.0f32,
|max_height, (cliff_pos, seed)| match Self::sample_column(
column_gen,
cache,
Vec2::from(*cliff_pos),
) {
Some(cliff_sample) if cliff_sample.cliffs && cliff_sample.spawn_rate > 0.5 => {
let cliff_pos3d = Vec3::from(*cliff_pos);
let height = RandomField::new(seed + 1).get(cliff_pos3d) % 48;
let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8;
max_height.max(
if cliff_pos.map(|e| e as f32).distance_squared(wpos)
< (radius * radius) as f32
{
cliff_sample.alt
+ height as f32 * (1.0 - cliff_sample.chaos)
+ cliff_hill
} else {
0.0
},
)
}
_ => max_height,
},
)
}
}
impl<'a> SamplerMut for BlockGen<'a> {
@ -41,71 +81,80 @@ impl<'a> SamplerMut for BlockGen<'a> {
type Sample = Option<Block>;
fn get(&mut self, wpos: Vec3<i32>) -> Option<Block> {
let BlockGen {
world,
column_cache,
column_gen,
} = self;
let ColumnSample {
alt,
chaos,
water_level,
river,
surface_color,
sub_surface_color,
tree_density,
forest_kind,
close_trees,
cave_xy,
cave_alt,
rock,
cliff,
cliffs,
cliff_hill,
close_cliffs,
temp,
} = self.sample_column(Vec2::from(wpos))?;
..
} = Self::sample_column(column_gen, column_cache, Vec2::from(wpos))?;
let wposf = wpos.map(|e| e as f64);
// Apply warping
let warp = (self
.world
.sim()
.gen_ctx
.warp_nz
.get((wposf.div(Vec3::new(150.0, 150.0, 150.0))).into_array())
as f32)
.mul((chaos - 0.1).max(0.0))
.mul(115.0);
let is_cliff = if cliff > 0.0 {
(self
.world
.sim()
.gen_ctx
.warp_nz
.get((wposf.div(Vec3::new(300.0, 300.0, 1500.0))).into_array()) as f32)
* cliff
> 0.3
} else {
false
};
let cliff = if is_cliff {
(0.0 + (self
.world
.sim()
.gen_ctx
.warp_nz
.get((wposf.div(Vec3::new(350.0, 350.0, 800.0))).into_array())
as f32)
* 0.8
+ (self
.world
let (definitely_underground, height, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping
(true, alt, water_level)
} else {
// Apply warping
let warp = (world
.sim()
.gen_ctx
.warp_nz
.get((wposf.div(Vec3::new(100.0, 100.0, 70.0))).into_array())
.get((wposf.div(Vec3::new(150.0, 150.0, 150.0))).into_array())
as f32)
* 0.3)
.add(0.4)
.mul(75.0)
} else {
0.0
};
.mul((chaos - 0.1).max(0.0))
.mul(115.0);
let height = alt + warp + cliff;
let height = if (wposf.z as f32) < alt + warp - 10.0 {
// Shortcut cliffs
alt + warp
} else {
let turb = Vec2::new(
world
.sim()
.gen_ctx
.turb_x_nz
.get((wposf.div(48.0)).into_array()) as f32,
world
.sim()
.gen_ctx
.turb_y_nz
.get((wposf.div(48.0)).into_array()) as f32,
) * 12.0;
let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb;
let cliff_height = Self::get_cliff_height(
column_gen,
column_cache,
wpos_turb,
&close_cliffs,
cliff_hill,
);
(alt + warp).max(cliff_height)
};
(false, height, water_level + warp)
};
// Sample blocks
@ -120,8 +169,13 @@ impl<'a> SamplerMut for BlockGen<'a> {
let water = Block::new(1, Rgb::new(100, 150, 255));
let warm_stone = Block::new(1, Rgb::new(165, 165, 130));
let block = if (wposf.z as f32) < height - 3.0 {
let col = Lerp::lerp(dirt_col, stone_col, (height - 4.0 - wposf.z as f32) * 0.15);
let grass_depth = 2.0;
let block = if (wposf.z as f32) < height - grass_depth {
let col = Lerp::lerp(
sub_surface_color.map(|e| (e * 255.0) as u8),
stone_col,
(height - grass_depth - wposf.z as f32) * 0.15,
);
// Underground
if (wposf.z as f32) > alt - 32.0 * chaos {
@ -131,13 +185,15 @@ impl<'a> SamplerMut for BlockGen<'a> {
}
} else if (wposf.z as f32) < height {
let col = Lerp::lerp(
dirt_col.map(|e| e as f32 / 255.0),
sub_surface_color,
surface_color,
(wposf.z as f32 - (height - 4.0)) * 0.25,
(wposf.z as f32 - (height - grass_depth))
.div(grass_depth)
.powf(0.5),
);
// Surface
Some(Block::new(1, col.map(|e| (e * 255.0) as u8)))
} else if (wposf.z as f32) < CONFIG.sea_level {
} else if (wposf.z as f32) < water_height {
// Ocean
Some(water)
} else {
@ -165,7 +221,19 @@ impl<'a> SamplerMut for BlockGen<'a> {
// Rocks
let block = block.or_else(|| {
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock {
Some(warm_stone)
let field0 = RandomField::new(world.sim().seed + 0);
let field1 = RandomField::new(world.sim().seed + 1);
let field2 = RandomField::new(world.sim().seed + 2);
Some(Block::new(
1,
stone_col
- Rgb::new(
field0.get(wpos) as u8 % 32,
field1.get(wpos) as u8 % 32,
field2.get(wpos) as u8 % 32,
),
))
} else {
None
}
@ -212,38 +280,60 @@ impl<'a> SamplerMut for BlockGen<'a> {
}
}
let block = match block {
Some(block) => block,
None => (&close_trees)
.iter()
.fold(air, |block, (tree_pos, tree_seed)| {
match self.sample_column(Vec2::from(*tree_pos)) {
Some(tree_sample)
if tree_sample.tree_density
> 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2 =>
{
let tree_pos3d =
Vec3::new(tree_pos.x, tree_pos.y, tree_sample.alt as i32);
let rpos = wpos - tree_pos3d;
let block = if definitely_underground {
block.unwrap_or(Block::empty())
} else {
match block {
Some(block) => block,
None => (&close_trees)
.iter()
.fold(air, |block, (tree_pos, tree_seed)| {
if !block.is_empty() {
block
} else {
match Self::sample_column(
column_gen,
column_cache,
Vec2::from(*tree_pos),
) {
Some(tree_sample)
if tree_sample.tree_density
> 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2
&& tree_sample.alt > tree_sample.water_level
&& tree_sample.spawn_rate > 0.5 =>
{
let cliff_height = Self::get_cliff_height(
column_gen,
column_cache,
tree_pos.map(|e| e as f32),
&tree_sample.close_cliffs,
cliff_hill,
);
let height = tree_sample.alt.max(cliff_height);
let tree_pos3d =
Vec3::new(tree_pos.x, tree_pos.y, height as i32);
let rpos = wpos - tree_pos3d;
let trees = tree::kinds(tree_sample.forest_kind); // Choose tree kind
let trees = tree::kinds(tree_sample.forest_kind); // Choose tree kind
block.or(trees[*tree_seed as usize % trees.len()]
.get((rpos * 128) / 128) // Scaling
.map(|b| {
block_from_structure(
*b,
rpos,
*tree_pos,
*tree_seed,
&tree_sample,
)
})
.unwrap_or(Block::empty()))
block.or(trees[*tree_seed as usize % trees.len()]
.get((rpos * 128) / 128) // Scaling
.map(|b| {
block_from_structure(
*b,
rpos,
*tree_pos,
*tree_seed,
&tree_sample,
)
})
.unwrap_or(Block::empty()))
}
_ => block,
}
}
_ => block,
}
}),
}),
}
};
Some(block)

View File

@ -1,10 +1,19 @@
use crate::{all::ForestKind, util::Sampler, World, CONFIG};
use crate::{
all::ForestKind,
sim::{Location, LocationInfo},
util::Sampler,
World, CONFIG,
};
use common::{
terrain::{Block, TerrainChunkSize},
vol::{VolSize, Vox},
};
use noise::NoiseFn;
use std::ops::{Add, Div, Mul, Neg, Sub};
use std::{
f32,
ops::{Add, Div, Mul, Neg, Sub},
sync::Arc,
};
use vek::*;
pub struct ColumnGen<'a> {
@ -19,29 +28,62 @@ impl<'a> ColumnGen<'a> {
impl<'a> Sampler for ColumnGen<'a> {
type Index = Vec2<i32>;
type Sample = Option<ColumnSample>;
type Sample = Option<ColumnSample<'a>>;
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample> {
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> {
let wposf = wpos.map(|e| e as f64);
let chunk_pos = wpos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e as u32 / sz
e / sz as i32
});
let sim = self.world.sim();
let turb = Vec2::new(
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
sim.gen_ctx.turb_y_nz.get((wposf.div(48.0)).into_array()) as f32,
) * 12.0;
let wposf_turb = wposf + turb.map(|e| e as f64);
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
let dryness = sim.get_interpolated(wpos, |chunk| chunk.dryness)?;
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let cliffiness = sim.get_interpolated(wpos, |chunk| chunk.cliffiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let forest_kind = sim.get(chunk_pos)?.forest_kind;
let sim_chunk = sim.get(chunk_pos)?;
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?
+ sim.gen_ctx.small_nz.get((wposf.div(256.0)).into_array()) as f32
* chaos.max(0.2)
* 64.0;
const RIVER_PROPORTION: f32 = 0.025;
let river = dryness
.abs()
.neg()
.add(RIVER_PROPORTION)
.div(RIVER_PROPORTION)
.max(0.0)
.mul((1.0 - (chaos - 0.15) * 20.0).max(0.0).min(1.0));
let cliff_hill =
(sim.gen_ctx.small_nz.get((wposf.div(128.0)).into_array()) as f32).mul(16.0);
let riverless_alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?
+ (sim.gen_ctx.small_nz.get((wposf.div(256.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.2))
.mul(64.0);
let cliffs = sim_chunk.cliffs;
let alt = riverless_alt
- (1.0 - river)
.mul(f32::consts::PI)
.cos()
.add(1.0)
.mul(0.5)
.mul(24.0);
let water_level = (riverless_alt - 4.0 - 5.0 * chaos).max(CONFIG.sea_level);
let rock = (sim.gen_ctx.small_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64)
@ -55,15 +97,18 @@ impl<'a> Sampler for ColumnGen<'a> {
let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
let marble = (0.0
+ (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32).mul(0.75)
+ (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32).mul(0.25))
.add(1.0)
.mul(0.5);
let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
.add(1.0)
.mul(0.5);
let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32)
.mul(0.75)
.add(1.0)
.mul(0.5)
.add(marble_small.mul(0.25));
// Colours
let cold_grass = Rgb::new(0.0, 0.3, 0.1);
let warm_grass = Rgb::new(0.35, 1.0, 0.05);
let cold_grass = Rgb::new(0.05, 0.2, 0.1);
let warm_grass = Rgb::new(0.15, 0.65, 0.05);
let cold_stone = Rgb::new(0.55, 0.7, 0.75);
let warm_stone = Rgb::new(0.65, 0.65, 0.35);
let beach_sand = Rgb::new(0.93, 0.84, 0.4);
@ -74,10 +119,14 @@ impl<'a> Sampler for ColumnGen<'a> {
let sand = Rgb::lerp(beach_sand, desert_sand, marble);
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
let dirt = Lerp::lerp(Rgb::new(0.2, 0.1, 0.05), Rgb::new(0.4, 0.25, 0.0), marble);
let turf = grass;
let ground = Rgb::lerp(
Rgb::lerp(
snow,
grass,
turf,
temp.sub(CONFIG.snow_temp)
.sub((marble - 0.5) * 0.05)
.mul(256.0),
@ -86,6 +135,66 @@ impl<'a> Sampler for ColumnGen<'a> {
temp.sub(CONFIG.desert_temp).mul(32.0),
);
// Work out if we're on a path or near a town
let dist_to_path = match &sim_chunk.location {
Some(loc) => {
let this_loc = &sim.locations[loc.loc_idx];
this_loc
.neighbours
.iter()
.map(|j| {
let other_loc = &sim.locations[*j];
// Find the two location centers
let near_0 = this_loc.center.map(|e| e as f32);
let near_1 = other_loc.center.map(|e| e as f32);
// Calculate distance to path between them
(0.0 + (near_1.y - near_0.y) * wposf_turb.x as f32
- (near_1.x - near_0.x) * wposf_turb.y as f32
+ near_1.x * near_0.y
- near_0.x * near_1.y)
.abs()
.div(near_0.distance(near_1))
})
.filter(|x| x.is_finite())
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f32::INFINITY)
}
None => f32::INFINITY,
};
let on_path = dist_to_path < 5.0 && !sim_chunk.near_cliffs; // || near_0.distance(wposf_turb.map(|e| e as f32)) < 150.0;
let (alt, ground) = if on_path {
(alt - 1.0, dirt)
} else {
(alt, ground)
};
// Cities
// TODO: In a later MR
/*
let building = match &sim_chunk.location {
Some(loc) => {
let loc = &sim.locations[loc.loc_idx];
let rpos = wposf.map2(loc.center, |a, b| a as f32 - b as f32) / 256.0 + 0.5;
if rpos.map(|e| e >= 0.0 && e < 1.0).reduce_and() {
(loc.settlement
.get_at(rpos)
.map(|b| b.seed % 20 + 10)
.unwrap_or(0)) as f32
} else {
0.0
}
}
None => 0.0,
};
let alt = alt + building;
*/
// Caves
let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get(
@ -99,7 +208,7 @@ impl<'a> Sampler for ColumnGen<'a> {
.mul((1.15 - chaos).min(1.0))
};
let cave_xy = cave_at(wposf);
let cave_alt = alt - 32.0
let cave_alt = alt - 24.0
+ (sim
.gen_ctx
.cave_1_nz
@ -108,15 +217,17 @@ impl<'a> Sampler for ColumnGen<'a> {
+ (sim
.gen_ctx
.cave_1_nz
.get(Vec2::new(wposf.x, wposf.y).div(300.0).into_array()) as f32)
.get(Vec2::new(wposf.x, wposf.y).div(500.0).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(8.0)
.mul(256.0);
.powf(15.0)
.mul(150.0);
Some(ColumnSample {
alt,
chaos,
water_level,
river,
surface_color: Rgb::lerp(
sand,
// Land
@ -133,34 +244,46 @@ impl<'a> Sampler for ColumnGen<'a> {
- marble * 24.0)
/ 12.0,
),
(alt - CONFIG.sea_level - 0.2 * CONFIG.mountain_scale) / 180.0,
(alt - CONFIG.sea_level - 0.3 * CONFIG.mountain_scale + marble * 128.0) / 100.0,
),
// Beach
(alt - CONFIG.sea_level - 2.0) / 5.0,
((alt - CONFIG.sea_level - 2.0) / 5.0).min(1.0 - river * 2.0),
),
sub_surface_color: dirt,
tree_density,
forest_kind,
forest_kind: sim_chunk.forest_kind,
close_trees: sim.gen_ctx.tree_gen.get(wpos),
cave_xy,
cave_alt,
rock,
cliff: cliffiness,
cliffs,
cliff_hill,
close_cliffs: sim.gen_ctx.cliff_gen.get(wpos),
temp,
spawn_rate,
location: sim_chunk.location.as_ref(),
})
}
}
#[derive(Clone)]
pub struct ColumnSample {
pub struct ColumnSample<'a> {
pub alt: f32,
pub chaos: f32,
pub water_level: f32,
pub river: f32,
pub surface_color: Rgb<f32>,
pub sub_surface_color: Rgb<f32>,
pub tree_density: f32,
pub forest_kind: ForestKind,
pub close_trees: [(Vec2<i32>, u32); 9],
pub cave_xy: f32,
pub cave_alt: f32,
pub rock: f32,
pub cliff: f32,
pub cliffs: bool,
pub cliff_hill: f32,
pub close_cliffs: [(Vec2<i32>, u32); 9],
pub temp: f32,
pub spawn_rate: f32,
pub location: Option<&'a LocationInfo>,
}

View File

@ -1,10 +1,10 @@
#![feature(euclidean_division, bind_by_move_pattern_guards)]
#![feature(euclidean_division, bind_by_move_pattern_guards, option_flattening)]
mod all;
mod block;
mod column;
pub mod config;
mod sim;
pub mod sim;
pub mod util;
// Reexports
@ -62,15 +62,21 @@ impl World {
let water = Block::new(5, Rgb::new(100, 150, 255));
let chunk_size2d = Vec2::from(TerrainChunkSize::SIZE);
let base_z = match self.sim.get_interpolated(
chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2),
|chunk| chunk.get_base_z(),
) {
Some(base_z) => base_z as i32,
let (base_z, sim_chunk) = match self
.sim
.get_interpolated(
chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2),
|chunk| chunk.get_base_z(),
)
.and_then(|base_z| self.sim.get(chunk_pos).map(|sim_chunk| (base_z, sim_chunk)))
{
Some((base_z, sim_chunk)) => (base_z as i32, sim_chunk),
None => return TerrainChunk::new(0, water, air, TerrainChunkMeta::void()),
};
let mut chunk = TerrainChunk::new(base_z - 8, stone, air, TerrainChunkMeta::void());
let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome());
let mut chunk = TerrainChunk::new(base_z - 8, stone, air, meta);
let mut sampler = self.sample_blocks();

View File

@ -1,46 +1,68 @@
use super::Settlement;
use fxhash::FxHashSet;
use rand::Rng;
#[derive(Copy, Clone, Debug)]
pub enum LocationKind {
Settlement,
Mountain,
Forest,
}
use vek::*;
#[derive(Clone, Debug)]
pub struct Location {
name: String,
kind: LocationKind,
kingdom: Kingdom,
pub(crate) name: String,
pub(crate) center: Vec2<i32>,
pub(crate) kingdom: Option<Kingdom>,
pub(crate) neighbours: FxHashSet<usize>,
pub(crate) settlement: Settlement,
}
impl Location {
pub fn generate(center: Vec2<i32>, rng: &mut impl Rng) -> Self {
Self {
name: generate_name(rng),
center,
kingdom: None,
neighbours: FxHashSet::default(),
settlement: Settlement::generate(rng),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn kingdom(&self) -> Option<&Kingdom> {
self.kingdom.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct Kingdom {
name: String,
region_name: String,
}
fn generate_name() -> String {
let consts = [
"st", "tr", "b", "n", "p", "ph", "cr", "g", "c", "d", "k", "kr", "kl", "gh", "sl", "st",
"cr", "sp", "th", "dr", "pr", "dr", "gr", "br", "ryth", "rh", "sl", "f", "fr", "p", "pr",
"qu", "s", "sh", "z", "k", "br", "wh", "tr", "h", "bl", "sl", "r", "kl", "sl", "w", "v",
"vr", "kr",
];
let vowels = [
"oo", "o", "oa", "au", "e", "ee", "ea", "ou", "u", "a", "i", "ie",
fn generate_name<R: Rng>(rng: &mut R) -> String {
let firstsyl = [
"Eri", "Val", "Gla", "Wilde", "Cold", "Deep", "Dura", "Ester", "Fay", "Dark", "West",
"East", "North", "South", "Ray", "Eri", "Dal", "Som", "Sommer", "Black", "Iron", "Grey",
"Hel", "Gal", "Mor", "Lo", "Nil", "Bel", "Lor", "Gold", "Red", "Marble", "Mana", "Gar",
"Mountain", "Red", "Cheo", "Far", "High",
];
let mid = ["ka", "se", "au", "da", "di"];
let tails = [
"er", "in", "o", "on", "an", "ar", "is", "oon", "er", "aru", "ab", "um", "id", "and",
"eld", "ald", "oft", "aft", "ift", "ity", "ell", "oll", "ill", "all",
/*"mill",*/ "ben", "sel", "dori", "theas", "dar", "bur", "to", "vis", "ten", "stone",
"tiva", "id", "and", "or", "el", "ond", "ia", "eld", "ald", "aft", "ift", "ity", "well",
"oll", "ill", "all", "wyn", "light", " Hill", "lin", "mont", "mor", "cliff", "rok", "den",
"mi", "rock", "glenn", "rovi", "lea", "gate", "view", "ley", "wood", "ovia", "cliff",
"marsh", "kor", "ice", /*"river",*/ "acre", "venn", "crest", "field", "vale",
"spring", " Vale", "grasp", "fel", "fall", "grove", "wyn", "edge",
];
let mut name = String::new();
for i in 0..rand::random::<u32>() % 2 {
name += rand::thread_rng().choose(&consts).unwrap();
name += rand::thread_rng().choose(&vowels).unwrap();
if rng.gen() {
name += rng.choose(&firstsyl).unwrap();
name += rng.choose(&mid).unwrap();
name += rng.choose(&tails).unwrap();
name
} else {
name += rng.choose(&firstsyl).unwrap();
name += rng.choose(&tails).unwrap();
name
}
name += rand::thread_rng().choose(&consts).unwrap();
name += rand::thread_rng().choose(&tails).unwrap();
name
}

View File

@ -1,9 +1,24 @@
mod location;
mod settlement;
use self::location::Location;
use crate::{all::ForestKind, util::StructureGen2d, CONFIG};
use common::{terrain::TerrainChunkSize, vol::VolSize};
use noise::{BasicMulti, HybridMulti, MultiFractal, NoiseFn, RidgedMulti, Seedable, SuperSimplex};
// Reexports
pub use self::location::Location;
pub use self::settlement::Settlement;
use crate::{
all::ForestKind,
util::{Sampler, StructureGen2d},
CONFIG,
};
use common::{
terrain::{BiomeKind, TerrainChunkSize},
vol::VolSize,
};
use noise::{
BasicMulti, HybridMulti, MultiFractal, NoiseFn, OpenSimplex, RidgedMulti, Seedable,
SuperSimplex,
};
use rand::{prng::XorShiftRng, Rng, SeedableRng};
use std::{
ops::{Add, Div, Mul, Neg, Sub},
sync::Arc,
@ -13,12 +28,13 @@ use vek::*;
pub const WORLD_SIZE: Vec2<usize> = Vec2 { x: 1024, y: 1024 };
pub(crate) struct GenCtx {
pub turb_x_nz: BasicMulti,
pub turb_y_nz: BasicMulti,
pub turb_x_nz: SuperSimplex,
pub turb_y_nz: SuperSimplex,
pub chaos_nz: RidgedMulti,
pub alt_nz: HybridMulti,
pub hill_nz: SuperSimplex,
pub temp_nz: SuperSimplex,
pub dry_nz: BasicMulti,
pub small_nz: BasicMulti,
pub rock_nz: HybridMulti,
pub cliff_nz: HybridMulti,
@ -29,19 +45,23 @@ pub(crate) struct GenCtx {
pub cave_1_nz: SuperSimplex,
pub tree_gen: StructureGen2d,
pub cliff_gen: StructureGen2d,
}
pub struct WorldSim {
pub seed: u32,
pub(crate) chunks: Vec<SimChunk>,
pub(crate) locations: Vec<Location>,
pub(crate) gen_ctx: GenCtx,
pub rng: XorShiftRng,
}
impl WorldSim {
pub fn generate(seed: u32) -> Self {
let mut gen_ctx = GenCtx {
turb_x_nz: BasicMulti::new().set_seed(seed + 0),
turb_y_nz: BasicMulti::new().set_seed(seed + 1),
turb_x_nz: SuperSimplex::new().set_seed(seed + 0),
turb_y_nz: SuperSimplex::new().set_seed(seed + 1),
chaos_nz: RidgedMulti::new().set_octaves(7).set_seed(seed + 2),
hill_nz: SuperSimplex::new().set_seed(seed + 3),
alt_nz: HybridMulti::new()
@ -49,23 +69,25 @@ impl WorldSim {
.set_persistence(0.1)
.set_seed(seed + 4),
temp_nz: SuperSimplex::new().set_seed(seed + 5),
small_nz: BasicMulti::new().set_octaves(2).set_seed(seed + 6),
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 7),
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 7),
warp_nz: BasicMulti::new().set_octaves(3).set_seed(seed + 8),
dry_nz: BasicMulti::new().set_seed(seed + 6),
small_nz: BasicMulti::new().set_octaves(2).set_seed(seed + 7),
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 8),
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 9),
warp_nz: BasicMulti::new().set_octaves(3).set_seed(seed + 10),
tree_nz: BasicMulti::new()
.set_octaves(12)
.set_persistence(0.75)
.set_seed(seed + 9),
cave_0_nz: SuperSimplex::new().set_seed(seed + 10),
cave_1_nz: SuperSimplex::new().set_seed(seed + 11),
.set_seed(seed + 12),
cave_0_nz: SuperSimplex::new().set_seed(seed + 13),
cave_1_nz: SuperSimplex::new().set_seed(seed + 14),
tree_gen: StructureGen2d::new(seed, 32, 24),
cliff_gen: StructureGen2d::new(seed, 80, 56),
};
let mut chunks = Vec::new();
for x in 0..WORLD_SIZE.x as u32 {
for y in 0..WORLD_SIZE.y as u32 {
for x in 0..WORLD_SIZE.x as i32 {
for y in 0..WORLD_SIZE.y as i32 {
chunks.push(SimChunk::generate(Vec2::new(x, y), &mut gen_ctx));
}
}
@ -73,21 +95,154 @@ impl WorldSim {
let mut this = Self {
seed,
chunks,
locations: Vec::new(),
gen_ctx,
rng: XorShiftRng::from_seed([
(seed >> 0) as u8,
0,
0,
0,
(seed >> 8) as u8,
0,
0,
0,
(seed >> 16) as u8,
0,
0,
0,
(seed >> 24) as u8,
0,
0,
0,
]),
};
this.simulate(100);
this.seed_elements();
this
}
pub fn simulate(&mut self, cycles: usize) {
// TODO
/// Prepare the world for simulation
pub fn seed_elements(&mut self) {
let mut rng = self.rng.clone();
let cell_size = 32;
let grid_size = WORLD_SIZE / cell_size;
let loc_count = 250;
let mut loc_grid = vec![None; grid_size.product()];
let mut locations = Vec::new();
// Seed the world with some locations
for _ in 0..loc_count {
let cell_pos = Vec2::new(
self.rng.gen::<usize>() % grid_size.x,
self.rng.gen::<usize>() % grid_size.y,
);
let wpos = (cell_pos * cell_size + cell_size / 2)
.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e as i32 * sz as i32 + sz as i32 / 2
});
locations.push(Location::generate(wpos, &mut rng));
loc_grid[cell_pos.y * grid_size.x + cell_pos.x] = Some(locations.len() - 1);
}
// Find neighbours
let mut loc_clone = locations
.iter()
.map(|l| l.center)
.enumerate()
.collect::<Vec<_>>();
for i in 0..locations.len() {
let pos = locations[i].center;
loc_clone.sort_by_key(|(_, l)| l.distance_squared(pos));
loc_clone.iter().skip(1).take(2).for_each(|(j, _)| {
locations[i].neighbours.insert(*j);
locations[*j].neighbours.insert(i);
});
}
// Simulate invasion!
let invasion_cycles = 25;
for _ in 0..invasion_cycles {
for i in 0..grid_size.x {
for j in 0..grid_size.y {
if loc_grid[j * grid_size.x + i].is_none() {
const R_COORDS: [i32; 5] = [-1, 0, 1, 0, -1];
let idx = self.rng.gen::<usize>() % 4;
let loc = Vec2::new(i as i32 + R_COORDS[idx], j as i32 + R_COORDS[idx + 1])
.map(|e| e as usize);
loc_grid[j * grid_size.x + i] =
loc_grid.get(loc.y * grid_size.x + loc.x).cloned().flatten();
}
}
}
}
// Place the locations onto the world
let gen = StructureGen2d::new(self.seed, cell_size as u32, cell_size as u32 / 2);
for i in 0..WORLD_SIZE.x {
for j in 0..WORLD_SIZE.y {
let chunk_pos = Vec2::new(i as i32, j as i32);
let block_pos = Vec2::new(
chunk_pos.x * TerrainChunkSize::SIZE.x as i32,
chunk_pos.y * TerrainChunkSize::SIZE.y as i32,
);
let cell_pos = Vec2::new(i / cell_size, j / cell_size);
// Find the distance to each region
let near = gen.get(chunk_pos);
let mut near = near
.iter()
.map(|(pos, seed)| RegionInfo {
chunk_pos: *pos,
block_pos: pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e * sz as i32
}),
dist: (pos - chunk_pos).map(|e| e as f32).magnitude(),
seed: *seed,
})
.collect::<Vec<_>>();
// Sort regions based on distance
near.sort_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap());
let nearest_cell_pos = near[0].chunk_pos.map(|e| e as usize) / cell_size;
self.get_mut(chunk_pos).unwrap().location = loc_grid
.get(nearest_cell_pos.y * grid_size.x + nearest_cell_pos.x)
.cloned()
.unwrap_or(None)
.map(|loc_idx| LocationInfo { loc_idx, near });
let town_size = 200;
let in_town = self
.get(chunk_pos)
.unwrap()
.location
.as_ref()
.map(|l| {
locations[l.loc_idx].center.distance_squared(block_pos)
< town_size * town_size
})
.unwrap_or(false);
if in_town {
self.get_mut(chunk_pos).unwrap().spawn_rate = 0.0;
}
}
}
self.rng = rng;
self.locations = locations;
}
pub fn get(&self, chunk_pos: Vec2<u32>) -> Option<&SimChunk> {
pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
if chunk_pos
.map2(WORLD_SIZE, |e, sz| e < sz as u32)
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
.reduce_and()
{
Some(&self.chunks[chunk_pos.y as usize * WORLD_SIZE.x + chunk_pos.x as usize])
@ -96,7 +251,18 @@ impl WorldSim {
}
}
pub fn get_base_z(&self, chunk_pos: Vec2<u32>) -> Option<f32> {
pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
if chunk_pos
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
.reduce_and()
{
Some(&mut self.chunks[chunk_pos.y as usize * WORLD_SIZE.x + chunk_pos.x as usize])
} else {
None
}
}
pub fn get_base_z(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
self.get(chunk_pos).and_then(|_| {
(0..2)
.map(|i| (0..2).map(move |j| (i, j)))
@ -134,11 +300,10 @@ impl WorldSim {
let mut x = [T::default(); 4];
for (x_idx, j) in (-1..3).enumerate() {
let y0 =
f(self.get(pos.map2(Vec2::new(j, -1), |e, q| (e.max(0.0) as i32 + q) as u32))?);
let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| (e.max(0.0) as i32 + q) as u32))?);
let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| (e.max(0.0) as i32 + q) as u32))?);
let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| (e.max(0.0) as i32 + q) as u32))?);
let y0 = f(self.get(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
}
@ -147,23 +312,40 @@ impl WorldSim {
}
}
const Z_TOLERANCE: (f32, f32) = (128.0, 96.0);
const Z_TOLERANCE: (f32, f32) = (100.0, 128.0);
pub struct SimChunk {
pub chaos: f32,
pub alt_base: f32,
pub alt: f32,
pub temp: f32,
pub dryness: f32,
pub rockiness: f32,
pub cliffiness: f32,
pub cliffs: bool,
pub near_cliffs: bool,
pub tree_density: f32,
pub forest_kind: ForestKind,
pub location: Option<Arc<Location>>,
pub spawn_rate: f32,
pub location: Option<LocationInfo>,
}
#[derive(Copy, Clone)]
pub struct RegionInfo {
pub chunk_pos: Vec2<i32>,
pub block_pos: Vec2<i32>,
pub dist: f32,
pub seed: u32,
}
#[derive(Clone)]
pub struct LocationInfo {
pub loc_idx: usize,
pub near: Vec<RegionInfo>,
}
impl SimChunk {
fn generate(pos: Vec2<u32>, gen_ctx: &mut GenCtx) -> Self {
let wposf = (pos * Vec2::from(TerrainChunkSize::SIZE)).map(|e| e as f64);
fn generate(pos: Vec2<i32>, gen_ctx: &mut GenCtx) -> Self {
let wposf = (pos * TerrainChunkSize::SIZE.map(|e| e as i32)).map(|e| e as f64);
let hill = (0.0
+ gen_ctx
@ -177,10 +359,29 @@ impl SimChunk {
.add(0.3)
.max(0.0);
let dryness = (gen_ctx.dry_nz.get(
(wposf
.add(Vec2::new(
gen_ctx
.dry_nz
.get((wposf.add(10000.0).div(500.0)).into_array())
* 150.0,
gen_ctx.dry_nz.get((wposf.add(0.0).div(500.0)).into_array()) * 150.0,
))
.div(2_000.0))
.into_array(),
) as f32);
let chaos = (gen_ctx.chaos_nz.get((wposf.div(4_000.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(1.4)
.mul(
(gen_ctx.chaos_nz.get((wposf.div(6_000.0)).into_array()) as f32)
.powf(2.0)
.add(0.5)
.min(1.0),
)
.powf(1.5)
.add(0.1 * hill);
let chaos = chaos + chaos.mul(16.0).sin().mul(0.02);
@ -191,9 +392,9 @@ impl SimChunk {
.add(alt_base.mul(128.0).sin().mul(0.005))
.mul(400.0);
let alt_main = (gen_ctx.alt_nz.get((wposf.div(1_000.0)).into_array()) as f32)
let alt_main = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32)
.abs()
.powf(1.7);
.powf(1.8);
let alt = CONFIG.sea_level
+ alt_base
@ -210,25 +411,27 @@ impl SimChunk {
let temp = (gen_ctx.temp_nz.get((wposf.div(8192.0)).into_array()) as f32);
let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 + chaos * 0.2;
Self {
chaos,
alt_base,
alt,
temp,
dryness,
rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
.sub(0.1)
.mul(1.3)
.max(0.0),
cliffiness: (gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32)
.sub(0.15)
.mul(3.0)
.mul(1.1 - chaos)
.max(0.0)
.min(1.0),
cliffs: cliff > 0.5
&& dryness > 0.05
&& alt > CONFIG.sea_level + 5.0
&& dryness.abs() > 0.075,
near_cliffs: cliff > 0.4,
tree_density: (gen_ctx.tree_nz.get((wposf.div(1024.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.mul(1.2 - chaos * 0.85)
.mul(1.2 - chaos * 0.95)
.add(0.1)
.mul(if alt > CONFIG.sea_level + 5.0 {
1.0
@ -248,6 +451,7 @@ impl SimChunk {
ForestKind::SnowPine
}
},
spawn_rate: 1.0,
location: None,
}
}
@ -257,10 +461,35 @@ impl SimChunk {
}
pub fn get_min_z(&self) -> f32 {
self.alt - Z_TOLERANCE.0 * (self.chaos + 0.3)
self.alt - Z_TOLERANCE.0 * (self.chaos * 1.2 + 0.3)
}
pub fn get_max_z(&self) -> f32 {
(self.alt + Z_TOLERANCE.1).max(CONFIG.sea_level + 1.0)
(self.alt + Z_TOLERANCE.1 * if self.near_cliffs { 1.0 } else { 0.5 })
.max(CONFIG.sea_level + 2.0)
}
pub fn get_name(&self, world: &WorldSim) -> Option<String> {
if let Some(loc) = &self.location {
Some(world.locations[loc.loc_idx].name().to_string())
} else {
None
}
}
pub fn get_biome(&self) -> BiomeKind {
if self.alt < CONFIG.sea_level {
BiomeKind::Ocean
} else if self.chaos > 0.6 {
BiomeKind::Mountain
} else if self.temp > CONFIG.desert_temp {
BiomeKind::Desert
} else if self.temp < CONFIG.snow_temp {
BiomeKind::Snowlands
} else if self.tree_density > 0.65 {
BiomeKind::Forest
} else {
BiomeKind::Grassland
}
}
}

View File

@ -0,0 +1,88 @@
use rand::Rng;
use vek::*;
#[derive(Clone, Debug)]
pub struct Settlement {
lot: Lot,
}
impl Settlement {
pub fn generate(rng: &mut impl Rng) -> Self {
Self {
lot: Lot::generate(0, 32.0, 1.0, rng),
}
}
pub fn get_at(&self, pos: Vec2<f32>) -> Option<&Building> {
self.lot.get_at(pos)
}
}
#[derive(Clone, Debug)]
pub struct Building {
pub seed: u32,
}
#[derive(Clone, Debug)]
enum Lot {
None,
One(Building),
Many { split_x: bool, lots: Vec<Lot> },
}
impl Lot {
pub fn generate(deep: usize, depth: f32, aspect: f32, rng: &mut impl Rng) -> Self {
let depth = if deep < 3 { 8.0 } else { depth };
if (depth < 1.0 || deep > 6) && !(deep < 3 || deep % 2 == 1) {
if rng.gen::<f32>() < 0.5 {
Lot::One(Building { seed: rng.gen() })
} else {
Lot::None
}
} else {
Lot::Many {
split_x: aspect > 1.0,
lots: {
let pow2 = 1 + rng.gen::<usize>() % 1;
let n = 1 << pow2;
let new_aspect = if aspect > 1.0 {
aspect / n as f32
} else {
aspect * n as f32
};
let vari = (rng.gen::<f32>() - 0.35) * 2.8;
let new_depth = depth * 0.5 * (1.0 + vari);
(0..n)
.map(|_| Lot::generate(deep + 1, new_depth, new_aspect, rng))
.collect()
},
}
}
}
pub fn get_at(&self, pos: Vec2<f32>) -> Option<&Building> {
match self {
Lot::None => None,
Lot::One(building) => {
if pos.map(|e| e > 0.1 && e < 0.9).reduce_and() {
Some(building)
} else {
None
}
}
Lot::Many { split_x, lots } => {
let split_dim = if *split_x { pos.x } else { pos.y };
let idx = (split_dim * lots.len() as f32).floor() as usize;
lots[idx.min(lots.len() - 1)].get_at(if *split_x {
Vec2::new((pos.x * lots.len() as f32).fract(), pos.y)
} else {
Vec2::new(pos.x, (pos.y * lots.len() as f32).fract())
})
}
}
}
}

View File

@ -18,24 +18,20 @@ impl Sampler for RandomField {
fn get(&self, pos: Self::Index) -> Self::Sample {
let pos = pos.map(|e| (e * 13 + (1 << 31)) as u32);
let next = self.seed.wrapping_mul(0x168E3D1F).wrapping_add(0xDEADBEAD);
let next = next
.rotate_left(13)
.wrapping_mul(133227)
.wrapping_add(pos.x);
let next = next.rotate_left(13).wrapping_mul(318912) ^ 0x42133742;
let next = next
.rotate_left(13)
.wrapping_mul(938219)
.wrapping_add(pos.y);
let next = next.rotate_left(13).wrapping_mul(318912) ^ 0x23341753;
let next = next
.rotate_left(13)
.wrapping_mul(938219)
.wrapping_add(pos.z);
let next = next.rotate_left(13).wrapping_mul(313322) ^ 0xDEADBEEF;
let next = next.rotate_left(13).wrapping_mul(929009) ^ 0xFF329DE3;
let next = next.rotate_left(13).wrapping_mul(422671) ^ 0x42892942;
next
let mut a = self.seed;
a = (a ^ 61) ^ (a >> 16);
a = a + (a << 3);
a = a ^ pos.x;
a = a ^ (a >> 4);
a = a * 0x27d4eb2d;
a = a ^ (a >> 15);
a = a ^ pos.y;
a = (a ^ 61) ^ (a >> 16);
a = a + (a << 3);
a = a ^ (a >> 4);
a = a ^ pos.z;
a = a * 0x27d4eb2d;
a = a ^ (a >> 15);
a
}
}