diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index a9316ac993..f653f1d30c 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -170,7 +170,7 @@ void main() { hsva_color.y *= 1.45; hsva_color.z *= 0.85; //hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0); - vec4 final_color = fxaa_color; + vec4 final_color = fxaa_color; //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a); tgt_color = vec4(final_color.rgb, 1); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index 14fe299613..bbfe953324 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -47,4 +47,4 @@ void main() { proj_mat * view_mat * vec4(f_pos, 1); -} \ No newline at end of file +} diff --git a/assets/world/module/human/balcony_upstairs.vox b/assets/world/module/human/balcony_upstairs.vox new file mode 100644 index 0000000000..49e0d6826e --- /dev/null +++ b/assets/world/module/human/balcony_upstairs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a61471613997d50f2e3739e6417df4c60dfc83b8091859e8ed038fb8aca79e55 +size 1416 diff --git a/assets/world/module/human/chimney_roof.vox b/assets/world/module/human/chimney_roof.vox new file mode 100644 index 0000000000..47a118d2a0 --- /dev/null +++ b/assets/world/module/human/chimney_roof.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77920b4223fd4a05145ef2dd38e5fea91c7b0b1eb72fe1efa4325059e7429eb9 +size 1916 diff --git a/assets/world/module/human/corner_ground.vox b/assets/world/module/human/corner_ground.vox new file mode 100644 index 0000000000..e182e55db5 --- /dev/null +++ b/assets/world/module/human/corner_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7642360b38812647493c24156f6ea85acad4b10019c417cf23b04e6756c4b021 +size 4012 diff --git a/assets/world/module/human/corner_roof.vox b/assets/world/module/human/corner_roof.vox new file mode 100644 index 0000000000..0571175893 --- /dev/null +++ b/assets/world/module/human/corner_roof.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd97dbd7f0e27d4320d2bc91bd6d7799a9d7dec02e22c8b9f447f8982a7186d7 +size 1676 diff --git a/assets/world/module/human/corner_upstairs.vox b/assets/world/module/human/corner_upstairs.vox new file mode 100644 index 0000000000..7a4552d1e8 --- /dev/null +++ b/assets/world/module/human/corner_upstairs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45a6cff3756302dcca46fce84ca6bb5998b25ef2bdbe43794e3e048c1ada0613 +size 4012 diff --git a/assets/world/module/human/door_big.vox b/assets/world/module/human/door_big.vox new file mode 100644 index 0000000000..a7b1fb2f37 --- /dev/null +++ b/assets/world/module/human/door_big.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea44b43263f79ca708b3b0fa196ed4eb2157521f4349b0c97524757950e4c59e +size 47099 diff --git a/assets/world/module/human/door_ground.vox b/assets/world/module/human/door_ground.vox new file mode 100644 index 0000000000..be08e4ac12 --- /dev/null +++ b/assets/world/module/human/door_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac34b6009acbce1d2a33bb527719b25e75e295d9f53ec83f1ecd45e0d4d3eb2f +size 3992 diff --git a/assets/world/module/human/floor_ground.vox b/assets/world/module/human/floor_ground.vox new file mode 100644 index 0000000000..bfb5262b55 --- /dev/null +++ b/assets/world/module/human/floor_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d143fd09da40dcdc3c26c662ed969dce98d6115c7d1fdc7a08e0be2a0fd1548 +size 4012 diff --git a/assets/world/module/human/floor_roof.vox b/assets/world/module/human/floor_roof.vox new file mode 100644 index 0000000000..a11cc4523e --- /dev/null +++ b/assets/world/module/human/floor_roof.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c58e5556c76ee6eac8d7f83af8ab66c237d85897fa62f8359872c15852df63bf +size 4012 diff --git a/assets/world/module/human/floor_upstairs.vox b/assets/world/module/human/floor_upstairs.vox new file mode 100644 index 0000000000..9b5c26e2fa --- /dev/null +++ b/assets/world/module/human/floor_upstairs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbcf0cf859fce0ecf8f302f669610317331a804cff57e5638a1872a52020c981 +size 4012 diff --git a/assets/world/module/human/stair_ground.vox b/assets/world/module/human/stair_ground.vox new file mode 100644 index 0000000000..bf6aafd99b --- /dev/null +++ b/assets/world/module/human/stair_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6e10cd6387dfbcf578f5dae2d243b8a35f17e2596856d7b8332ac05809f826b +size 2040 diff --git a/assets/world/module/human/wall_ground.vox b/assets/world/module/human/wall_ground.vox new file mode 100644 index 0000000000..091e29aaf2 --- /dev/null +++ b/assets/world/module/human/wall_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:973892a68060cc889ec4b726dcb9794249442c2c6f3a59c6e8d4958f8cb1fb2e +size 4012 diff --git a/assets/world/module/human/wall_roof.vox b/assets/world/module/human/wall_roof.vox new file mode 100644 index 0000000000..6a1e5a12ff --- /dev/null +++ b/assets/world/module/human/wall_roof.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b884335c307d2d0e82712c477c7ce33ad2f6b6ecdaa08a432142300c335b1ee +size 4012 diff --git a/assets/world/module/human/wall_upstairs.vox b/assets/world/module/human/wall_upstairs.vox new file mode 100644 index 0000000000..745551a64b --- /dev/null +++ b/assets/world/module/human/wall_upstairs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8dac53854429015007dfd44242e902f1d04fe1f11ef818a30225564607cef6e +size 4012 diff --git a/assets/world/module/human/window_ground.vox b/assets/world/module/human/window_ground.vox new file mode 100644 index 0000000000..56d4f885c7 --- /dev/null +++ b/assets/world/module/human/window_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a84d68e3741154a0d4c9732e4f037e3efe67ca5f6c2e94d192c0773f88b6a6b6 +size 4012 diff --git a/assets/world/module/human/window_upstairs.vox b/assets/world/module/human/window_upstairs.vox new file mode 100644 index 0000000000..df35d807f3 --- /dev/null +++ b/assets/world/module/human/window_upstairs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa292366a8bc36921c42705db54e3c4ac88d7c833c20f4729b524b9f94aa7803 +size 4012 diff --git a/assets/world/module/wall/corner_ground.vox b/assets/world/module/wall/corner_ground.vox new file mode 100644 index 0000000000..768eca471b --- /dev/null +++ b/assets/world/module/wall/corner_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45748af2727abb70d77986e85dd9af2383381fd66efac50228d6d9eb068709e6 +size 2716 diff --git a/assets/world/module/wall/corner_mid.vox b/assets/world/module/wall/corner_mid.vox new file mode 100644 index 0000000000..bb36fa2b9e --- /dev/null +++ b/assets/world/module/wall/corner_mid.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bfcb87955e2c7d3c05b818ec0275fc6e6942b91423941d7056fbb16cadea98b +size 2660 diff --git a/assets/world/module/wall/corner_top.vox b/assets/world/module/wall/corner_top.vox new file mode 100644 index 0000000000..224239b371 --- /dev/null +++ b/assets/world/module/wall/corner_top.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b185a18df4c044445463670caa04cbc9662398f799069a73b3c7b556ae542b78 +size 3312 diff --git a/assets/world/module/wall/edge_ground.vox b/assets/world/module/wall/edge_ground.vox new file mode 100644 index 0000000000..5f646c9192 --- /dev/null +++ b/assets/world/module/wall/edge_ground.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c60695392ae89d3bf9ba8927d25c39b1da5eb173ac174238720604b9ca982eda +size 2608 diff --git a/assets/world/module/wall/edge_mid.vox b/assets/world/module/wall/edge_mid.vox new file mode 100644 index 0000000000..d894dfc34f --- /dev/null +++ b/assets/world/module/wall/edge_mid.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5212e3429fd9f1877a3bdd6017400960c817cfae75c4e4f2f03509f6c07757f +size 2536 diff --git a/assets/world/module/wall/edge_top.vox b/assets/world/module/wall/edge_top.vox new file mode 100644 index 0000000000..cbeea4032a --- /dev/null +++ b/assets/world/module/wall/edge_top.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e53559945372e8e5924f6b6c7b97d6d35c9c7a5b4050b0c553e27a080587a8b9 +size 3268 diff --git a/assets/world/module/wall/end_top.vox b/assets/world/module/wall/end_top.vox new file mode 100644 index 0000000000..8e246035af --- /dev/null +++ b/assets/world/module/wall/end_top.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df2dadec6a2f6862f9d7397051ae3ba74d6ed0fe5447a402893ab192dfca5735 +size 2120 diff --git a/assets/world/module/wall/single_top.vox b/assets/world/module/wall/single_top.vox new file mode 100644 index 0000000000..dbf9f207d2 --- /dev/null +++ b/assets/world/module/wall/single_top.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:038b24785d2894a2fb15a479fe0277a3be1fcbadb02459bf5293cffaf4e7767a +size 3188 diff --git a/assets/world/structure/human/mage_tower.vox b/assets/world/structure/human/mage_tower.vox index 7df09363c0..0d2115c4c3 100644 --- a/assets/world/structure/human/mage_tower.vox +++ b/assets/world/structure/human/mage_tower.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6ed2f574af13b79f6cb6df5450068cc637372a24a739a892d55d4b1ff4f287b -size 125468 +oid sha256:db4f7b604c35763c28aeafcfe7dc1d1722b2db77838677ffa5ef4fb08581be9d +size 41004 diff --git a/server-cli/.gitignore b/server-cli/.gitignore new file mode 100644 index 0000000000..dc83325b30 --- /dev/null +++ b/server-cli/.gitignore @@ -0,0 +1 @@ +settings.ron diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index b3d479a672..d3543d7aad 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -104,8 +104,8 @@ impl + ReadVol + Debug, S: VolSize + Clone> .map(|vox| block_shadow_density(vox.kind())) .unwrap_or((0.0, 0.0)); - neighbour_light[0][i][j] = - (neighbour_light[0][i][j] * (1.0 - density)).max(cap); + neighbour_light[0][i][j] = (neighbour_light[0][i][j] * (1.0 - density)) + .max(cap.min(neighbour_light[1][i][j])); } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index d01e5e52fa..451402e298 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -608,11 +608,9 @@ impl Terrain { } } - // Translucent + // Terrain sprites for (pos, chunk) in &self.chunks { if chunk.visible { - renderer.render_fluid_chunk(&chunk.fluid_model, globals, &chunk.locals, lights); - const SPRITE_RENDER_DISTANCE: f32 = 128.0; let chunk_center = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { @@ -632,5 +630,12 @@ impl Terrain { } } } + + // Translucent + for (_, chunk) in &self.chunks { + if chunk.visible { + renderer.render_fluid_chunk(&chunk.fluid_model, globals, &chunk.locals, lights); + } + } } } diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 22e9d530e4..b73ae6fb96 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -2,6 +2,7 @@ mod natural; use crate::{ column::{ColumnGen, ColumnSample, StructureData}, + generator::{Generator, TownGen}, util::{HashCache, RandomField, Sampler, SamplerMut}, World, CONFIG, }; @@ -136,6 +137,7 @@ impl<'a> BlockGen<'a> { column_gen, } = self; + let sample = &z_cache?.sample; let &ColumnSample { alt, chaos, @@ -155,8 +157,10 @@ impl<'a> BlockGen<'a> { cliff_hill, close_cliffs, temp, + + chunk, .. - } = &z_cache?.sample; + } = sample; let structures = &z_cache?.structures; @@ -168,31 +172,21 @@ impl<'a> BlockGen<'a> { (true, alt, CONFIG.sea_level /*water_level*/) } else { // Apply warping - let warp = (world - .sim() - .gen_ctx - .warp_nz - .get((wposf.div(Vec3::new(150.0, 150.0, 150.0))).into_array()) - as f32) + let warp = (world.sim().gen_ctx.warp_nz.get(wposf.div(48.0)) as f32) .mul((chaos - 0.1).max(0.0)) - .mul(96.0); + .mul(48.0) + + (world.sim().gen_ctx.warp_nz.get(wposf.div(15.0)) as f32) + .mul((chaos - 0.1).max(0.0)) + .mul(24.0); 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; + world.sim().gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32, + world.sim().gen_ctx.fast_turb_y_nz.get(wposf.div(25.0)) as f32, + ) * 8.0; let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb; let cliff_height = Self::get_cliff_height( @@ -352,6 +346,14 @@ impl<'a> BlockGen<'a> { } }); + // Structures (like towns) + let block = chunk + .structures + .town + .as_ref() + .and_then(|town| TownGen.get((town, wpos, sample, height))) + .or(block); + let block = structures .iter() .find_map(|st| { @@ -406,11 +408,24 @@ impl<'a> ZCache<'a> { .max(self.sample.water_level) .max(CONFIG.sea_level + 2.0); + // Structures + let (min, max) = self + .sample + .chunk + .structures + .town + .as_ref() + .map(|town| { + let (town_min, town_max) = TownGen.get_z_limits(town, self.wpos, &self.sample); + (town_min.min(min), town_max.max(max)) + }) + .unwrap_or((min, max)); + (min, max) } } -impl<'a> SamplerMut for BlockGen<'a> { +impl<'a> SamplerMut<'static> for BlockGen<'a> { type Index = Vec3; type Sample = Option; @@ -499,7 +514,7 @@ impl StructureInfo { } } -fn block_from_structure( +pub fn block_from_structure( sblock: StructureBlock, default_kind: BlockKind, pos: Vec3, diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index 79fde45685..54c6f27306 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -30,6 +30,7 @@ pub fn structure_gen<'a>( if (st_sample.tree_density as f64) < random_seed || st_sample.alt < st_sample.water_level || st_sample.spawn_rate < 0.5 + || !st_sample.spawn_rules.trees { return None; } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index c89575e1e4..04c1903ae0 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,7 +1,8 @@ use crate::{ all::ForestKind, block::StructureMeta, - sim::{LocationInfo, SimChunk}, + generator::{Generator, SpawnRules, TownGen}, + sim::{LocationInfo, SimChunk, WorldSim}, util::{RandomPerm, Sampler, UnitChooser}, World, CONFIG, }; @@ -20,7 +21,7 @@ use std::{ use vek::*; pub struct ColumnGen<'a> { - world: &'a World, + pub sim: &'a WorldSim, } static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); @@ -55,14 +56,13 @@ lazy_static! { } impl<'a> ColumnGen<'a> { - pub fn new(world: &'a World) -> Self { - Self { world } + pub fn new(sim: &'a WorldSim) -> Self { + Self { sim } } fn get_local_structure(&self, wpos: Vec2) -> Option { let (pos, seed) = self - .world - .sim() + .sim .gen_ctx .region_gen .get(wpos) @@ -74,7 +74,7 @@ impl<'a> ColumnGen<'a> { let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { e / sz as i32 }); - let chunk = self.world.sim().get(chunk_pos)?; + let chunk = self.sim.get(chunk_pos)?; if seed % 5 == 2 && chunk.temp > CONFIG.desert_temp @@ -102,8 +102,7 @@ impl<'a> ColumnGen<'a> { fn gen_close_structures(&self, wpos: Vec2) -> [Option; 9] { let mut metas = [None; 9]; - self.world - .sim() + self.sim .gen_ctx .structure_gen .get(wpos) @@ -121,7 +120,7 @@ impl<'a> ColumnGen<'a> { } } -impl<'a> Sampler for ColumnGen<'a> { +impl<'a> Sampler<'a> for ColumnGen<'a> { type Index = Vec2; type Sample = Option>; @@ -131,7 +130,7 @@ impl<'a> Sampler for ColumnGen<'a> { e / sz as i32 }); - let sim = self.world.sim(); + let sim = &self.sim; let turb = Vec2::new( sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32, @@ -142,7 +141,6 @@ impl<'a> Sampler for ColumnGen<'a> { 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 humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?; let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?; let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; @@ -175,8 +173,16 @@ impl<'a> Sampler for ColumnGen<'a> { .small_nz .get((wposf_turb.div(150.0)).into_array()) as f32) .abs() - .mul(chaos.max(0.15)) - .mul(64.0); + .mul(chaos.max(0.025)) + .mul(64.0) + + (sim + .gen_ctx + .small_nz + .get((wposf_turb.div(450.0)).into_array()) as f32) + .abs() + .mul(1.0 - chaos) + .mul(1.0 - humidity) + .mul(96.0); let is_cliffs = sim_chunk.is_cliffs; let near_cliffs = sim_chunk.near_cliffs; @@ -376,6 +382,7 @@ impl<'a> Sampler for ColumnGen<'a> { .add((marble_small - 0.5) * 0.5), ); + /* // Work out if we're on a path or near a town let dist_to_path = match &sim_chunk.location { Some(loc) => { @@ -412,9 +419,11 @@ impl<'a> Sampler for ColumnGen<'a> { } else { (alt, ground) }; + */ // Cities // TODO: In a later MR + /* let building = match &sim_chunk.location { Some(loc) => { let loc = &sim.locations[loc.loc_idx]; @@ -433,6 +442,7 @@ impl<'a> Sampler for ColumnGen<'a> { }; let alt = alt + building; + */ // Caves let cave_at = |wposf: Vec2| { @@ -507,6 +517,14 @@ impl<'a> Sampler for ColumnGen<'a> { temp, spawn_rate, location: sim_chunk.location.as_ref(), + + chunk: sim_chunk, + spawn_rules: sim_chunk + .structures + .town + .as_ref() + .map(|town| TownGen.spawn_rules(town, wpos)) + .unwrap_or(SpawnRules::default()), }) } } @@ -534,6 +552,9 @@ pub struct ColumnSample<'a> { pub temp: f32, pub spawn_rate: f32, pub location: Option<&'a LocationInfo>, + + pub chunk: &'a SimChunk, + pub spawn_rules: SpawnRules, } #[derive(Copy, Clone)] diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs new file mode 100644 index 0000000000..b658a4b41b --- /dev/null +++ b/world/src/generator/mod.rs @@ -0,0 +1,34 @@ +mod town; + +// Reexports +pub use self::town::{TownGen, TownState}; + +use crate::{column::ColumnSample, util::Sampler}; +use common::terrain::Block; +use vek::*; + +#[derive(Copy, Clone, Debug)] +pub struct SpawnRules { + pub trees: bool, +} + +impl Default for SpawnRules { + fn default() -> Self { + Self { trees: true } + } +} + +impl SpawnRules { + pub fn and(self, other: Self) -> Self { + Self { + trees: self.trees && other.trees, + } + } +} + +pub trait Generator<'a, T: 'a>: + Sampler<'a, Index = (&'a T, Vec3, &'a ColumnSample<'a>, f32), Sample = Option> +{ + fn get_z_limits(&self, state: &'a T, wpos: Vec2, sample: &ColumnSample) -> (f32, f32); + fn spawn_rules(&self, town: &'a TownState, wpos: Vec2) -> SpawnRules; +} diff --git a/world/src/generator/town/mod.rs b/world/src/generator/town/mod.rs new file mode 100644 index 0000000000..181b3874b5 --- /dev/null +++ b/world/src/generator/town/mod.rs @@ -0,0 +1,655 @@ +mod util; +mod vol; + +use super::{Generator, SpawnRules}; +use crate::{ + block::block_from_structure, + column::{ColumnGen, ColumnSample}, + sim::WorldSim, + util::{seed_expan, Grid, Sampler, UnitChooser}, +}; +use common::{ + assets, + terrain::{Block, BlockKind, Structure}, + vol::{ReadVol, Vox, WriteVol}, +}; +use hashbrown::HashSet; +use lazy_static::lazy_static; +use rand::prelude::*; +use rand_chacha::ChaChaRng; +use std::{ops::Add, sync::Arc}; +use vek::*; + +use self::vol::{CellKind, ColumnKind, Module, TownCell, TownColumn, TownVol}; + +const CELL_SIZE: i32 = 9; +const CELL_HEIGHT: i32 = 9; + +pub struct TownGen; + +impl<'a> Sampler<'a> for TownGen { + type Index = (&'a TownState, Vec3, &'a ColumnSample<'a>, f32); + type Sample = Option; + + fn get(&self, (town, wpos, sample, height): Self::Index) -> Self::Sample { + let cell_pos = (wpos - town.center) + .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| { + e.div_euclid(sz) + }) + .add(Vec3::from(town.vol.size() / 2)); + let inner_pos = (wpos - town.center) + .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| { + e.rem_euclid(sz) + }); + + let cell = town.vol.get(cell_pos).ok()?; + + match (modules_from_kind(&cell.kind), &cell.module) { + (Some(module_list), Some(module)) => { + let transform = [ + (Vec2::new(0, 0), Vec2::unit_x(), Vec2::unit_y()), + (Vec2::new(0, 1), -Vec2::unit_y(), Vec2::unit_x()), + (Vec2::new(1, 1), -Vec2::unit_x(), -Vec2::unit_y()), + (Vec2::new(1, 0), Vec2::unit_y(), -Vec2::unit_x()), + ]; + + module_list[module.vol_idx] + .0 + .get( + Vec3::from( + transform[module.dir].0 * (CELL_SIZE - 1) + + transform[module.dir].1 * inner_pos.x + + transform[module.dir].2 * inner_pos.y, + ) + Vec3::unit_z() * inner_pos.z, + ) + .ok() + .and_then(|sb| { + block_from_structure(*sb, BlockKind::Normal, wpos, wpos.into(), 0, sample) + }) + } + _ => match cell.kind { + CellKind::Empty => None, + CellKind::Park => None, + CellKind::Rock => Some(Block::new(BlockKind::Normal, Rgb::broadcast(100))), + CellKind::Wall => Some(Block::new(BlockKind::Normal, Rgb::broadcast(175))), + CellKind::Road => { + if (wpos.z as f32) < height - 1.0 { + Some(Block::new( + BlockKind::Normal, + Lerp::lerp( + Rgb::new(150.0, 140.0, 50.0), + Rgb::new(100.0, 95.0, 30.0), + sample.marble_small, + ) + .map(|e| e as u8), + )) + } else { + Some(Block::empty()) + } + } + CellKind::House(idx) => Some(Block::new(BlockKind::Normal, town.houses[idx].color)), + }, + } + } +} + +impl<'a> Generator<'a, TownState> for TownGen { + fn get_z_limits( + &self, + town: &'a TownState, + wpos: Vec2, + sample: &ColumnSample, + ) -> (f32, f32) { + (sample.alt - 32.0, sample.alt + 75.0) + } + + fn spawn_rules(&self, town: &'a TownState, wpos: Vec2) -> SpawnRules { + SpawnRules { trees: false } + } +} + +struct House { + color: Rgb, +} + +pub struct TownState { + center: Vec3, + radius: i32, + vol: TownVol, + houses: Vec, +} + +impl TownState { + pub fn generate(center: Vec2, gen: &mut ColumnGen, rng: &mut impl Rng) -> Option { + let radius = rng.gen_range(18, 20) * 9; + let size = Vec2::broadcast(radius * 2 / 9 - 2); + + let alt = gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) as i32; + + let mut vol = TownVol::generate_from( + size, + |pos| { + let wpos = center + (pos - size / 2) * CELL_SIZE + CELL_SIZE / 2; + let rel_alt = gen.get(wpos).map(|sample| sample.alt).unwrap_or(0.0) as i32 + + CELL_HEIGHT / 2 + - alt; + + let col = TownColumn { + ground: rel_alt.div_euclid(CELL_HEIGHT), + kind: None, + }; + + (col.ground, col) + }, + |(col, pos)| { + if pos.z >= col.ground { + TownCell::empty() + } else { + TownCell::from(CellKind::Rock) + } + }, + ); + + // Generation passes + vol.setup(rng); + vol.gen_roads(rng, 30); + vol.gen_parks(rng, 3); + vol.emplace_columns(); + let houses = vol.gen_houses(rng, 50); + vol.gen_walls(rng); + vol.resolve_modules(rng); + vol.cull_unused(); + + Some(Self { + center: Vec3::new(center.x, center.y, alt), + radius, + vol, + houses, + }) + } + + pub fn center(&self) -> Vec3 { + self.center + } + + pub fn radius(&self) -> i32 { + self.radius + } +} + +impl TownVol { + fn floodfill( + &self, + limit: Option, + mut opens: HashSet>, + mut f: impl FnMut(Vec2, &TownColumn) -> bool, + ) -> HashSet> { + let mut closed = HashSet::new(); + + while opens.len() > 0 { + let mut new_opens = HashSet::new(); + + 'search: for open in opens.iter() { + for i in -1..2 { + for j in -1..2 { + let pos = *open + Vec2::new(i, j); + + if let Some(col) = self.col(pos) { + if !closed.contains(&pos) && !opens.contains(&pos) && f(pos, col) { + match limit { + Some(limit) + if limit + <= new_opens.len() + closed.len() + opens.len() => + { + break 'search + } + _ => { + new_opens.insert(pos); + } + } + } + } + } + } + } + + closed = closed.union(&opens).copied().collect(); + opens = new_opens; + } + + closed + } + + fn setup(&mut self, rng: &mut impl Rng) { + // Place a single road tile at first + let root_road = self + .size() + .map(|sz| (sz / 8) * 2 + rng.gen_range(0, sz / 4) * 2); + self.set_col_kind(root_road, Some(ColumnKind::Road)); + } + + fn gen_roads(&mut self, rng: &mut impl Rng, n: usize) { + const ATTEMPTS: usize = 5; + + let mut junctions = HashSet::new(); + junctions.insert(self.choose_column(rng, |_, col| col.is_road()).unwrap()); + + for road in 0..n { + for _ in 0..ATTEMPTS { + let start = *junctions.iter().choose(rng).unwrap(); + //let start = self.choose_column(rng, |pos, col| pos.map(|e| e % 2 == 0).reduce_and() && col.is_road()).unwrap(); + let dir = util::gen_dir(rng); + + // If the direction we want to paint a path in is obstructed, abandon this attempt + if self + .col(start + dir) + .map(|col| !col.is_empty()) + .unwrap_or(true) + { + continue; + } + + // How long should this road be? + let len = rng.gen_range(1, 10) * 2 + 1; + + // Paint the road until we hit an obstacle + let success = (1..len) + .map(|i| start + dir * i) + .try_for_each(|pos| { + if self.col(pos).map(|col| col.is_empty()).unwrap_or(false) { + self.set_col_kind(pos, Some(ColumnKind::Road)); + Ok(()) + } else { + junctions.insert(pos); + Err(()) + } + }) + .is_ok(); + + if success { + junctions.insert(start + dir * (len - 1)); + } + + break; + } + } + } + + fn gen_parks(&mut self, rng: &mut impl Rng, n: usize) { + const ATTEMPTS: usize = 5; + + for _ in 0..n { + for _ in 0..ATTEMPTS { + let start = self + .choose_column(rng, |pos, col| { + col.is_empty() + && (0..4).any(|i| { + self.col(pos + util::dir(i)) + .map(|col| col.is_road()) + .unwrap_or(false) + }) + }) + .unwrap(); + + let mut park = + self.floodfill(Some(16), [start].iter().copied().collect(), |_, col| { + col.is_empty() + }); + + if park.len() < 4 { + continue; + } + + for cell in park { + self.set_col_kind(cell, Some(ColumnKind::Internal)); + let col = self.col(cell).unwrap(); + let ground = col.ground; + for z in 0..2 { + self.set(Vec3::new(cell.x, cell.y, ground + z), CellKind::Park.into()); + } + } + + break; + } + } + } + + fn gen_walls(&mut self, rng: &mut impl Rng) { + let mut outer = HashSet::new(); + for i in 0..self.size().x { + outer.insert(Vec2::new(i, 0)); + outer.insert(Vec2::new(i, self.size().y - 1)); + } + for j in 0..self.size().y { + outer.insert(Vec2::new(0, j)); + outer.insert(Vec2::new(self.size().x - 1, j)); + } + + let mut outer = self.floodfill(None, outer, |_, col| col.is_empty()); + + let mut walls = HashSet::new(); + let inner = self.floodfill( + None, + [self.size() / 2].iter().copied().collect(), + |pos, _| { + if outer.contains(&pos) { + walls.insert(pos); + false + } else { + true + } + }, + ); + + while let Some(wall) = walls + .iter() + .filter(|pos| { + let lateral_count = (0..4) + .filter(|i| walls.contains(&(**pos + util::dir(*i)))) + .count(); + let max_quadrant_count = (0..4) + .map(|i| { + let units = util::unit(i); + (0..2) + .map(|i| (0..2).map(move |j| (i, j))) + .flatten() + .filter(|(i, j)| walls.contains(&(**pos + units.0 * *i + units.1 * *j))) + .count() + }) + .max() + .unwrap(); + + lateral_count < 2 || (lateral_count == 2 && max_quadrant_count == 4) + }) + .next() + { + let wall = *wall; + walls.remove(&wall); + } + + for wall in walls.iter() { + let col = self.col(*wall).unwrap(); + let ground = col.ground; + for z in -1..3 { + self.set(Vec3::new(wall.x, wall.y, ground + z), CellKind::Wall.into()); + } + } + } + + fn emplace_columns(&mut self) { + for i in 0..self.size().x { + for j in 0..self.size().y { + let col = self.col(Vec2::new(i, j)).unwrap(); + let ground = col.ground; + + match col.kind { + None => {} + Some(ColumnKind::Internal) => {} + Some(ColumnKind::External) => {} + Some(ColumnKind::Road) => { + for z in -1..2 { + self.set(Vec3::new(i, j, ground + z), CellKind::Road.into()); + } + } + _ => unimplemented!(), + } + } + } + } + + fn gen_houses(&mut self, rng: &mut impl Rng, n: usize) -> Vec { + const ATTEMPTS: usize = 10; + + let mut houses = Vec::new(); + for _ in 0..n { + for _ in 0..ATTEMPTS { + let entrance = { + let start = self.choose_cell(rng, |_, cell| cell.is_road()).unwrap(); + let dir = Vec3::from(util::gen_dir(rng)); + + if self + .get(start + dir) + .map(|col| !col.is_empty()) + .unwrap_or(true) + || self + .get(start + dir - Vec3::unit_z()) + .map(|col| !col.is_foundation()) + .unwrap_or(true) + { + continue; + } else { + start + dir + } + }; + + let mut cells: HashSet<_> = Some(entrance).into_iter().collect(); + + let mut energy = 1000; + while energy > 0 { + energy -= 1; + + let parent = *cells.iter().choose(rng).unwrap(); + let dir = util::UNITS_3D + .choose_weighted(rng, |pos| 1 + pos.z.max(0)) + .unwrap(); + + if self + .get(parent + dir) + .map(|cell| cell.is_empty()) + .unwrap_or(false) + && self + .get(parent + dir - Vec3::unit_z()) + .map(|cell| { + cell.is_foundation() + || cells.contains(&(parent + dir - Vec3::unit_z())) + }) + .unwrap_or(false) + { + cells.insert(parent + dir); + energy -= 10; + } + } + + // Remove cells that are too isolated + loop { + let cells_copy = cells.clone(); + + let mut any_removed = false; + cells.retain(|pos| { + let neighbour_count = (0..6) + .filter(|i| { + let neighbour = pos + util::dir_3d(*i); + cells_copy.contains(&neighbour) + }) + .count(); + + if neighbour_count < 3 { + any_removed = true; + false + } else { + true + } + }); + + if !any_removed { + break; + } + } + + // Get rid of houses that are too small + if cells.len() < 6 { + continue; + } + + for cell in cells { + self.set(cell, CellKind::House(houses.len()).into()); + self.set_col_kind(Vec2::from(cell), Some(ColumnKind::Internal)); + } + + houses.push(House { + color: Rgb::new(rng.gen(), rng.gen(), rng.gen()), + }); + } + } + + houses + } + + fn cull_unused(&mut self) { + for x in 0..self.size().x { + for y in 0..self.size().y { + for z in self.col_range(Vec2::new(x, y)).unwrap().rev() { + let pos = Vec3::new(x, y, z); + + // Remove foundations that don't have anything on top of them + if self.get(pos).unwrap().is_foundation() + && self + .get(pos + Vec3::unit_z()) + .map(TownCell::is_space) + .unwrap_or(true) + { + self.set(pos, TownCell::empty()); + } + } + } + } + } + + fn resolve_modules(&mut self, rng: &mut impl Rng) { + fn classify(cell: &TownCell, this_cell: &TownCell) -> ModuleKind { + match (&cell.kind, &this_cell.kind) { + (CellKind::House(a), CellKind::House(b)) if a == b => ModuleKind::This, + (CellKind::Wall, CellKind::Wall) => ModuleKind::This, + _ => ModuleKind::That, + } + } + + for x in 0..self.size().x { + for y in 0..self.size().y { + for z in self.col_range(Vec2::new(x, y)).unwrap() { + let pos = Vec3::new(x, y, z); + let this_cell = if let Ok(this_cell) = self.get(pos) { + this_cell + } else { + continue; + }; + + let mut signature = [ModuleKind::That; 6]; + for i in 0..6 { + signature[i] = self + .get(pos + util::dir_3d(i)) + .map(|cell| classify(cell, this_cell)) + .unwrap_or(ModuleKind::That); + } + + let module_list = if let Some(modules) = modules_from_kind(&this_cell.kind) { + modules + } else { + continue; + }; + + let module = module_list + .iter() + .enumerate() + .filter_map(|(i, module)| { + let perms = [[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], [1, 2, 3, 0]]; + + let mut rotated_signature = [ModuleKind::That; 6]; + for (dir, perm) in perms.iter().enumerate() { + rotated_signature[perm[0]] = signature[0]; + rotated_signature[perm[1]] = signature[1]; + rotated_signature[perm[2]] = signature[2]; + rotated_signature[perm[3]] = signature[3]; + rotated_signature[4] = signature[4]; + rotated_signature[5] = signature[5]; + + if &module.1[0..6] == &rotated_signature[0..6] { + return Some(Module { vol_idx: i, dir }); + } + } + + None + }) + .choose(rng); + + if let Some(module) = module { + let kind = this_cell.kind.clone(); + self.set( + pos, + TownCell { + kind, + module: Some(module), + }, + ); + } + } + } + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum ModuleKind { + This, + That, +} + +fn module(name: &str, sig: [ModuleKind; 6]) -> (Arc, [ModuleKind; 6]) { + ( + assets::load(&format!("world.module.{}", name)).unwrap(), + sig, + ) +} + +fn modules_from_kind(kind: &CellKind) -> Option<&'static [(Arc, [ModuleKind; 6])]> { + match kind { + CellKind::House(_) => Some(&HOUSE_MODULES), + CellKind::Wall => Some(&WALL_MODULES), + _ => None, + } +} + +lazy_static! { + pub static ref HOUSE_MODULES: Vec<(Arc, [ModuleKind; 6])> = { + use ModuleKind::*; + vec![ + module("human.floor_ground", [This, This, This, This, This, That]), + module("human.stair_ground", [This, This, This, This, This, That]), + module("human.corner_ground", [This, This, That, That, This, That]), + module("human.wall_ground", [This, This, This, That, This, That]), + module("human.door_ground", [This, This, This, That, This, That]), + module("human.window_ground", [This, This, This, That, This, That]), + module("human.floor_roof", [This, This, This, This, That, This]), + module("human.corner_roof", [This, This, That, That, That, This]), + module("human.chimney_roof", [This, This, That, That, That, This]), + module("human.wall_roof", [This, This, This, That, That, This]), + module("human.floor_upstairs", [This, This, This, This, This, This]), + module( + "human.balcony_upstairs", + [This, This, This, This, This, This], + ), + module( + "human.corner_upstairs", + [This, This, That, That, This, This], + ), + module("human.wall_upstairs", [This, This, This, That, This, This]), + module( + "human.window_upstairs", + [This, This, This, That, This, This], + ), + ] + }; + pub static ref WALL_MODULES: Vec<(Arc, [ModuleKind; 6])> = { + use ModuleKind::*; + vec![ + module("wall.edge_ground", [This, That, This, That, This, That]), + module("wall.edge_mid", [This, That, This, That, This, This]), + module("wall.edge_top", [This, That, This, That, That, This]), + module("wall.corner_ground", [This, This, That, That, This, That]), + module("wall.corner_mid", [This, This, That, That, This, This]), + module("wall.corner_top", [This, This, That, That, That, This]), + module("wall.end_top", [That, This, That, That, That, This]), + module("wall.single_top", [That, That, That, That, That, This]), + ] + }; +} diff --git a/world/src/generator/town/util.rs b/world/src/generator/town/util.rs new file mode 100644 index 0000000000..99cce9c784 --- /dev/null +++ b/world/src/generator/town/util.rs @@ -0,0 +1,42 @@ +use rand::prelude::*; +use vek::*; + +pub const UNITS: [Vec2; 4] = [ + Vec2 { x: 1, y: 0 }, + Vec2 { x: 0, y: 1 }, + Vec2 { x: -1, y: 0 }, + Vec2 { x: 0, y: -1 }, +]; + +pub fn dir(i: usize) -> Vec2 { + UNITS[i % 4] +} + +pub fn unit(i: usize) -> (Vec2, Vec2) { + (UNITS[i % 4], UNITS[(i + 1) % 4]) +} + +pub fn gen_unit(rng: &mut impl Rng) -> (Vec2, Vec2) { + unit(rng.gen_range(0, 4)) +} + +pub fn gen_dir(rng: &mut impl Rng) -> Vec2 { + UNITS[rng.gen_range(0, 4)] +} + +pub const UNITS_3D: [Vec3; 6] = [ + Vec3 { x: 1, y: 0, z: 0 }, + Vec3 { x: 0, y: 1, z: 0 }, + Vec3 { x: -1, y: 0, z: 0 }, + Vec3 { x: 0, y: -1, z: 0 }, + Vec3 { x: 0, y: 0, z: 1 }, + Vec3 { x: 0, y: 0, z: -1 }, +]; + +pub fn dir_3d(i: usize) -> Vec3 { + UNITS_3D[i % 6] +} + +pub fn gen_dir_3d(rng: &mut impl Rng) -> Vec3 { + UNITS_3D[rng.gen_range(0, 6)] +} diff --git a/world/src/generator/town/vol.rs b/world/src/generator/town/vol.rs new file mode 100644 index 0000000000..ecc610697c --- /dev/null +++ b/world/src/generator/town/vol.rs @@ -0,0 +1,227 @@ +use crate::util::Grid; +use common::vol::{BaseVol, ReadVol, Vox, WriteVol}; +use rand::prelude::*; +use std::ops::Range; +use vek::*; + +#[derive(Clone)] +pub enum ColumnKind { + Road, + Wall, + Internal, + External, // Outside the boundary wall +} + +#[derive(Clone, Default)] +pub struct TownColumn { + pub ground: i32, + pub kind: Option, +} + +impl TownColumn { + pub fn is_empty(&self) -> bool { + self.kind.is_none() + } + + pub fn is_road(&self) -> bool { + self.kind + .as_ref() + .map(|kind| match kind { + ColumnKind::Road => true, + _ => false, + }) + .unwrap_or(false) + } +} + +#[derive(Clone)] +pub struct Module { + pub vol_idx: usize, + pub dir: usize, +} + +#[derive(Clone)] +pub enum CellKind { + Empty, + Park, + Rock, + Road, + Wall, + House(usize), +} + +#[derive(Clone)] +pub struct TownCell { + pub kind: CellKind, + pub module: Option, +} + +impl TownCell { + pub fn is_road(&self) -> bool { + match self.kind { + CellKind::Road => true, + _ => false, + } + } + + pub fn is_space(&self) -> bool { + match self.kind { + CellKind::Empty => true, + CellKind::Park => true, + CellKind::Road => true, + _ => false, + } + } + + pub fn is_foundation(&self) -> bool { + match self.kind { + CellKind::Rock => true, + _ => false, + } + } +} + +impl Vox for TownCell { + fn empty() -> Self { + Self { + kind: CellKind::Empty, + module: None, + } + } + + fn is_empty(&self) -> bool { + match self.kind { + CellKind::Empty => true, + _ => false, + } + } +} + +impl From for TownCell { + fn from(kind: CellKind) -> Self { + Self { kind, module: None } + } +} + +#[derive(Debug)] +pub enum TownError { + OutOfBounds, +} + +const HEIGHT: usize = 24; +const UNDERGROUND_DEPTH: i32 = 5; + +type GridItem = (i32, TownColumn, Vec); + +pub struct TownVol { + grid: Grid, +} + +impl TownVol { + pub fn generate_from( + size: Vec2, + mut f: impl FnMut(Vec2) -> (i32, TownColumn), + mut g: impl FnMut((&TownColumn, Vec3)) -> TownCell, + ) -> Self { + let mut this = Self { + grid: Grid::new( + (0, TownColumn::default(), vec![TownCell::empty(); HEIGHT]), + size, + ), + }; + + for (pos, (base, col, cells)) in this.grid.iter_mut() { + let column = f(pos); + *base = column.0; + *col = column.1; + for z in 0..HEIGHT { + cells[z] = g(( + col, + Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + z as i32), + )); + } + } + + this + } + + pub fn size(&self) -> Vec2 { + self.grid.size() + } + + pub fn set_col_kind(&mut self, pos: Vec2, kind: Option) { + self.grid.get_mut(pos).map(|col| col.1.kind = kind); + } + + pub fn col(&self, pos: Vec2) -> Option<&TownColumn> { + self.grid.get(pos).map(|col| &col.1) + } + + pub fn col_range(&self, pos: Vec2) -> Option> { + self.grid.get(pos).map(|col| { + let lower = col.0 - UNDERGROUND_DEPTH; + lower..lower + HEIGHT as i32 + }) + } + + pub fn choose_column( + &self, + rng: &mut impl Rng, + mut f: impl FnMut(Vec2, &TownColumn) -> bool, + ) -> Option> { + self.grid + .iter() + .filter(|(pos, col)| f(*pos, &col.1)) + .choose(rng) + .map(|(pos, _)| pos) + } + + pub fn choose_cell( + &self, + rng: &mut impl Rng, + mut f: impl FnMut(Vec3, &TownCell) -> bool, + ) -> Option> { + self.grid + .iter() + .map(|(pos, (base, _, cells))| { + cells.iter().enumerate().map(move |(i, cell)| { + ( + Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + i as i32), + cell, + ) + }) + }) + .flatten() + .filter(|(pos, cell)| f(*pos, *cell)) + .choose(rng) + .map(|(pos, _)| pos) + } +} + +impl BaseVol for TownVol { + type Vox = TownCell; + type Err = TownError; +} + +impl ReadVol for TownVol { + fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Err> { + match self.grid.get(Vec2::from(pos)) { + Some((base, _, cells)) => cells + .get((pos.z + UNDERGROUND_DEPTH - *base) as usize) + .ok_or(TownError::OutOfBounds), + None => Err(TownError::OutOfBounds), + } + } +} + +impl WriteVol for TownVol { + fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), Self::Err> { + match self.grid.get_mut(Vec2::from(pos)) { + Some((base, _, cells)) => cells + .get_mut((pos.z + UNDERGROUND_DEPTH - *base) as usize) + .map(|cell| *cell = vox) + .ok_or(TownError::OutOfBounds), + None => Err(TownError::OutOfBounds), + } + } +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 5a58627c9c..cb0deb7f1f 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -3,13 +3,15 @@ const_generics, euclidean_division, bind_by_move_pattern_guards, - option_flattening + option_flattening, + label_break_value )] mod all; mod block; mod column; pub mod config; +pub mod generator; pub mod sim; pub mod util; @@ -56,11 +58,11 @@ impl World { pub fn sample_columns( &self, ) -> impl Sampler, Sample = Option> + '_ { - ColumnGen::new(self) + ColumnGen::new(&self.sim) } pub fn sample_blocks(&self) -> BlockGen { - BlockGen::new(self, ColumnGen::new(self)) + BlockGen::new(self, ColumnGen::new(&self.sim)) } pub fn generate_chunk(&self, chunk_pos: Vec2) -> (TerrainChunk, ChunkSupplement) { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 5be3ae1e69..97a921f9d1 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -11,7 +11,9 @@ use self::util::{ use crate::{ all::ForestKind, - util::{seed_expan, Sampler, StructureGen2d}, + column::ColumnGen, + generator::TownState, + util::{seed_expan, FastNoise, Sampler, StructureGen2d}, CONFIG, }; use common::{ @@ -24,8 +26,10 @@ use noise::{ use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; use std::{ + collections::HashMap, f32, ops::{Add, Div, Mul, Neg, Sub}, + sync::Arc, }; use vek::*; @@ -73,7 +77,7 @@ pub(crate) struct GenCtx { pub small_nz: BasicMulti, pub rock_nz: HybridMulti, pub cliff_nz: HybridMulti, - pub warp_nz: BasicMulti, + pub warp_nz: FastNoise, pub tree_nz: BasicMulti, pub cave_0_nz: SuperSimplex, @@ -82,6 +86,11 @@ pub(crate) struct GenCtx { pub structure_gen: StructureGen2d, pub region_gen: StructureGen2d, pub cliff_gen: StructureGen2d, + + pub fast_turb_x_nz: FastNoise, + pub fast_turb_y_nz: FastNoise, + + pub town_gen: StructureGen2d, } pub struct WorldSim { @@ -115,7 +124,7 @@ impl WorldSim { small_nz: BasicMulti::new().set_octaves(2).set_seed(gen_seed()), rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()), cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()), - warp_nz: BasicMulti::new().set_octaves(3).set_seed(gen_seed()), + warp_nz: FastNoise::new(gen_seed()), //BasicMulti::new().set_octaves(3).set_seed(gen_seed()), tree_nz: BasicMulti::new() .set_octaves(12) .set_persistence(0.75) @@ -133,6 +142,11 @@ impl WorldSim { // .set_octaves(6) // .set_persistence(0.5) .set_seed(gen_seed()), + + fast_turb_x_nz: FastNoise::new(gen_seed()), + fast_turb_y_nz: FastNoise::new(gen_seed()), + + town_gen: StructureGen2d::new(gen_seed(), 2048, 1024), }; // "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied value is @@ -202,7 +216,7 @@ impl WorldSim { // maximal at 0. Also to be multiplied by CONFIG.mountain_scale. let alt_main = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32) .abs() - .powf(1.45); + .powf(1.35); (0.0 + alt_main + (gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32) @@ -410,6 +424,40 @@ impl WorldSim { } } + // Stage 2 - towns! + let mut maybe_towns = HashMap::new(); + 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 wpos = chunk_pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { + e * sz as i32 + sz as i32 / 2 + }); + + let near_towns = self.gen_ctx.town_gen.get(wpos); + let town = near_towns + .iter() + .min_by_key(|(pos, seed)| wpos.distance_squared(*pos)); + + if let Some((pos, _)) = town { + let maybe_town = maybe_towns + .entry(*pos) + .or_insert_with(|| { + TownState::generate(*pos, &mut ColumnGen::new(self), &mut rng) + .map(|t| Arc::new(t)) + }) + .as_mut() + // Only care if we're close to the town + .filter(|town| { + Vec2::from(town.center()).distance_squared(wpos) + < town.radius().add(64).pow(2) + }) + .cloned(); + + self.get_mut(chunk_pos).unwrap().structures.town = maybe_town; + } + } + } + self.rng = rng; self.locations = locations; } @@ -425,6 +473,12 @@ impl WorldSim { } } + pub fn get_wpos(&self, wpos: Vec2) -> Option<&SimChunk> { + self.get(wpos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { + e / sz as i32 + })) + } + pub fn get_mut(&mut self, chunk_pos: Vec2) -> Option<&mut SimChunk> { if chunk_pos .map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32) @@ -491,7 +545,6 @@ pub struct SimChunk { pub alt_base: f32, pub alt: f32, pub temp: f32, - pub dryness: f32, pub humidity: f32, pub rockiness: f32, pub is_cliffs: bool, @@ -500,6 +553,8 @@ pub struct SimChunk { pub forest_kind: ForestKind, pub spawn_rate: f32, pub location: Option, + + pub structures: Structures, } #[derive(Copy, Clone)] @@ -516,27 +571,16 @@ pub struct LocationInfo { pub near: Vec, } +#[derive(Clone)] +pub struct Structures { + pub town: Option>, +} + impl SimChunk { fn generate(posi: usize, gen_ctx: &mut GenCtx, gen_cdf: &GenCdf) -> Self { let pos = uniform_idx_as_vec2(posi); let wposf = (pos * TerrainChunkSize::SIZE.map(|e| e as i32)).map(|e| e as f64); - // FIXME: Currently unused, but should represent fresh groundwater level. - // Should be correlated a little with humidity, somewhat negatively with altitude, - // and very negatively with difference in temperature from zero. - 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 (_, alt_base) = gen_cdf.alt_base[posi]; let map_edge_factor = map_edge_factor(posi); let (_, chaos) = gen_cdf.chaos[posi]; @@ -606,17 +650,13 @@ impl SimChunk { alt_base, alt, temp, - dryness, humidity, rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32) .sub(0.1) .mul(1.3) .max(0.0), - is_cliffs: cliff > 0.5 - && dryness > 0.05 - && alt > CONFIG.sea_level + 5.0 - && dryness.abs() > 0.075, - near_cliffs: cliff > 0.25, + is_cliffs: cliff > 0.5 && alt > CONFIG.sea_level + 5.0, + near_cliffs: cliff > 0.2, tree_density, forest_kind: if temp > 0.0 { if temp > CONFIG.desert_temp { @@ -678,6 +718,8 @@ impl SimChunk { }, spawn_rate: 1.0, location: None, + + structures: Structures { town: None }, } } diff --git a/world/src/util/fast_noise.rs b/world/src/util/fast_noise.rs new file mode 100644 index 0000000000..e25a61e980 --- /dev/null +++ b/world/src/util/fast_noise.rs @@ -0,0 +1,49 @@ +use super::{RandomField, Sampler}; +use std::f32; +use vek::*; + +pub struct FastNoise { + noise: RandomField, +} + +impl FastNoise { + pub const fn new(seed: u32) -> Self { + Self { + noise: RandomField::new(seed), + } + } + + fn noise_at(&self, pos: Vec3) -> f32 { + (self.noise.get(pos) % 4096) as f32 / 4096.0 + } +} + +impl Sampler<'static> for FastNoise { + type Index = Vec3; + type Sample = f32; + + fn get(&self, pos: Self::Index) -> Self::Sample { + let near_pos = pos.map(|e| e.floor() as i32); + + let v000 = self.noise_at(near_pos + Vec3::new(0, 0, 0)); + let v100 = self.noise_at(near_pos + Vec3::new(1, 0, 0)); + let v010 = self.noise_at(near_pos + Vec3::new(0, 1, 0)); + let v110 = self.noise_at(near_pos + Vec3::new(1, 1, 0)); + let v001 = self.noise_at(near_pos + Vec3::new(0, 0, 1)); + let v101 = self.noise_at(near_pos + Vec3::new(1, 0, 1)); + let v011 = self.noise_at(near_pos + Vec3::new(0, 1, 1)); + let v111 = self.noise_at(near_pos + Vec3::new(1, 1, 1)); + + let factor = pos.map(|e| 0.5 - (e.fract() as f32 * f32::consts::PI).cos() * 0.5); + + let x00 = Lerp::lerp(v000, v100, factor.x); + let x10 = Lerp::lerp(v010, v110, factor.x); + let x01 = Lerp::lerp(v001, v101, factor.x); + let x11 = Lerp::lerp(v011, v111, factor.x); + + let y0 = Lerp::lerp(x00, x10, factor.y); + let y1 = Lerp::lerp(x01, x11, factor.y); + + Lerp::lerp(y0, y1, factor.z) * 2.0 - 1.0 + } +} diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs new file mode 100644 index 0000000000..188f96c3ef --- /dev/null +++ b/world/src/util/grid.rs @@ -0,0 +1,74 @@ +use vek::*; + +pub struct Grid { + cells: Vec, + size: Vec2, +} + +impl Grid { + pub fn new(default_cell: T, size: Vec2) -> Self { + Self { + cells: vec![default_cell; size.product() as usize], + size, + } + } + + fn idx(&self, pos: Vec2) -> Option { + if pos.map2(self.size, |e, sz| e >= 0 && e < sz).reduce_and() { + Some((pos.y * self.size.x + pos.x) as usize) + } else { + None + } + } + + pub fn size(&self) -> Vec2 { + self.size + } + + pub fn get(&self, pos: Vec2) -> Option<&T> { + self.cells.get(self.idx(pos)?) + } + + pub fn get_mut(&mut self, pos: Vec2) -> Option<&mut T> { + let idx = self.idx(pos)?; + self.cells.get_mut(idx) + } + + pub fn set(&mut self, pos: Vec2, cell: T) -> Option<()> { + let idx = self.idx(pos)?; + self.cells.get_mut(idx).map(|c| *c = cell) + } + + pub fn iter(&self) -> impl Iterator, &T)> + '_ { + let w = self.size.x; + self.cells + .iter() + .enumerate() + .map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell)) + } + + pub fn iter_mut(&mut self) -> impl Iterator, &mut T)> + '_ { + let w = self.size.x; + self.cells + .iter_mut() + .enumerate() + .map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell)) + } + + pub fn iter_area( + &self, + pos: Vec2, + size: Vec2, + ) -> impl Iterator, &T)>> + '_ { + (0..size.x) + .map(move |x| { + (0..size.y).map(move |y| { + Some(( + pos + Vec2::new(x, y), + &self.cells[self.idx(pos + Vec2::new(x, y))?], + )) + }) + }) + .flatten() + } +} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index f0a44c87c8..218901d903 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -1,3 +1,5 @@ +pub mod fast_noise; +pub mod grid; pub mod hash_cache; pub mod random; pub mod sampler; @@ -7,6 +9,8 @@ pub mod unit_chooser; // Reexports pub use self::{ + fast_noise::FastNoise, + grid::Grid, hash_cache::HashCache, random::{RandomField, RandomPerm}, sampler::{Sampler, SamplerMut}, diff --git a/world/src/util/random.rs b/world/src/util/random.rs index ece87a30ce..da56a76ee2 100644 --- a/world/src/util/random.rs +++ b/world/src/util/random.rs @@ -11,26 +11,26 @@ impl RandomField { } } -impl Sampler for RandomField { +impl Sampler<'static> for RandomField { type Index = Vec3; type Sample = u32; fn get(&self, pos: Self::Index) -> Self::Sample { - let pos = pos.map(|e| (e * 13 + (1 << 31)) as u32); + let pos = pos.map(|e| u32::from_le_bytes(e.to_le_bytes())); let mut a = self.seed; a = (a ^ 61) ^ (a >> 16); - a = a + (a << 3); + a = a.wrapping_add(a << 3); a = a ^ pos.x; a = a ^ (a >> 4); - a = a * 0x27d4eb2d; + a = a.wrapping_mul(0x27d4eb2d); a = a ^ (a >> 15); a = a ^ pos.y; a = (a ^ 61) ^ (a >> 16); - a = a + (a << 3); + a = a.wrapping_add(a << 3); a = a ^ (a >> 4); a = a ^ pos.z; - a = a * 0x27d4eb2d; + a = a.wrapping_mul(0x27d4eb2d); a = a ^ (a >> 15); a } @@ -46,17 +46,19 @@ impl RandomPerm { } } -impl Sampler for RandomPerm { +impl Sampler<'static> for RandomPerm { type Index = u32; type Sample = u32; fn get(&self, perm: Self::Index) -> Self::Sample { - let mut a = perm; - a = (a ^ 61) ^ (a >> 16); - a = a + (a << 3); - a = a ^ (a >> 4); - a = a * 0x27d4eb2d; - a = a ^ (a >> 15); - a + let a = self + .seed + .wrapping_mul(3471) + .wrapping_add(perm) + .wrapping_add(0x3BE7172B) + .wrapping_mul(perm) + .wrapping_add(0x172A3BE1); + let b = a.wrapping_mul(a); + b ^ (a >> 17) ^ b >> 15 } } diff --git a/world/src/util/sampler.rs b/world/src/util/sampler.rs index 921ffc7f33..14a720949e 100644 --- a/world/src/util/sampler.rs +++ b/world/src/util/sampler.rs @@ -1,13 +1,13 @@ -pub trait Sampler: Sized { - type Index; - type Sample; +pub trait Sampler<'a>: Sized { + type Index: 'a; + type Sample: 'a; fn get(&self, index: Self::Index) -> Self::Sample; } -pub trait SamplerMut: Sized { - type Index; - type Sample; +pub trait SamplerMut<'a>: Sized { + type Index: 'a; + type Sample: 'a; fn get(&mut self, index: Self::Index) -> Self::Sample; } diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index 512acd671d..6c2e570c49 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -21,7 +21,7 @@ impl StructureGen2d { } } -impl Sampler for StructureGen2d { +impl Sampler<'static> for StructureGen2d { type Index = Vec2; type Sample = [(Vec2, u32); 9]; diff --git a/world/src/util/unit_chooser.rs b/world/src/util/unit_chooser.rs index c5c0203090..882126e0da 100644 --- a/world/src/util/unit_chooser.rs +++ b/world/src/util/unit_chooser.rs @@ -24,7 +24,7 @@ impl UnitChooser { } } -impl Sampler for UnitChooser { +impl Sampler<'static> for UnitChooser { type Index = u32; type Sample = (Vec2, Vec2);