diff --git a/assets/voxygen/shaders/particle-frag.glsl b/assets/voxygen/shaders/particle-frag.glsl index 92cebd6e21..3e5433341a 100644 --- a/assets/voxygen/shaders/particle-frag.glsl +++ b/assets/voxygen/shaders/particle-frag.glsl @@ -89,7 +89,7 @@ void main() { : compute_attenuation_point(f_pos, -view_dir, vec3(0), fluid_alt, /*cam_pos.z <= fluid_alt ? cam_pos.xyz : f_pos*/cam_pos.xyz); #endif - max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, f_pos, MU_WATER, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light); + max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light); max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light); diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 10b8617f12..1362f94d57 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -90,6 +90,24 @@ impl BlockKind { /// fields. #[inline] pub const fn has_color(&self) -> bool { self.is_filled() } + + /// Determine whether the block is 'terrain-like'. This definition is + /// arbitrary, but includes things like rocks, soils, sands, grass, and + /// other blocks that might be expected to the landscape. Plant matter and + /// snow are *not* included. + #[inline] + pub const fn is_terrain(&self) -> bool { + matches!( + self, + BlockKind::Rock + | BlockKind::WeakRock + | BlockKind::GlowingRock + | BlockKind::GlowingWeakRock + | BlockKind::Grass + | BlockKind::Earth + | BlockKind::Sand + ) + } } #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index 9db64ae900..b6526c647b 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -198,7 +198,7 @@ pub fn init( _ => {}, }, SiteKind::Refactor(site2) => { - for _ in 0..(site.economy.pop as usize).min(site2.plots().len() * 3) { + for _ in 0..site.economy.pop.min(site2.plots().len() as f32 * 1.5) as usize { rtsim.entities.insert(Entity { is_loaded: false, pos: site2 @@ -217,7 +217,7 @@ pub fn init( }); } - for _ in 0..site2.plazas().len() * 3 { + for _ in 0..(site2.plazas().len() as f32 * 1.5) as usize { rtsim.entities.insert(Entity { is_loaded: false, pos: site2 diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 42f3871523..bace8d5d1d 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,6 +1,7 @@ use crate::{ all::ForestKind, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim}, + site::SpawnRules, util::{RandomField, Sampler}, IndexRef, CONFIG, }; @@ -109,6 +110,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let neighbor_chunk = sim.get(neighbor_pos)?; Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river)) }); + let spawn_rules = sim_chunk + .sites + .iter() + .map(|site| index.sites[*site].spawn_rules(wpos)) + .fold(SpawnRules::default(), |a, b| a.combine(b)); let gradient = sim.get_gradient_approx(chunk_pos); @@ -829,12 +835,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; - let warp_factor = warp_factor - * sim_chunk - .sites - .iter() - .map(|site| index.sites[*site].spawn_rules(wpos).max_warp) - .fold(1.0f32, |a, b| a.min(b)); + let warp_factor = warp_factor * spawn_rules.max_warp; let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); let alt = alt + riverless_alt_delta; @@ -1120,7 +1121,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // dirt let ground = Lerp::lerp(ground, sub_surface_color, marble_mid * tree_density); - let path = sim.get_nearest_path(wpos); + let path = if spawn_rules.paths { + sim.get_nearest_path(wpos) + } else { + None + }; let cave = sim.get_nearest_cave(wpos); let ice_depth = if snow_factor < -0.25 @@ -1168,11 +1173,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // No growing directly on bedrock. // And, no growing on sites that don't want them TODO: More precise than this when we // apply trees as a post-processing layer - tree_density: if sim_chunk - .sites - .iter() - .all(|site| index.sites[*site].spawn_rules(wpos).trees) - { + tree_density: if spawn_rules.trees { Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)) } else { 0.0 diff --git a/world/src/land.rs b/world/src/land.rs index 8384251647..5a3d32611e 100644 --- a/world/src/land.rs +++ b/world/src/land.rs @@ -21,15 +21,19 @@ impl<'a> Land<'a> { pub fn get_gradient_approx(&self, wpos: Vec2) -> f32 { self.sim - .and_then(|sim| { - sim.get_gradient_approx( - wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)), - ) - }) + .and_then(|sim| sim.get_gradient_approx(self.wpos_chunk_pos(wpos))) .unwrap_or(0.0) } - pub fn get_chunk_at(&self, wpos: Vec2) -> Option<&sim::SimChunk> { + pub fn wpos_chunk_pos(&self, wpos: Vec2) -> Vec2 { + wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)) + } + + pub fn get_chunk(&self, chunk_pos: Vec2) -> Option<&sim::SimChunk> { + self.sim.and_then(|sim| sim.get(chunk_pos)) + } + + pub fn get_chunk_wpos(&self, wpos: Vec2) -> Option<&sim::SimChunk> { self.sim.and_then(|sim| sim.get_wpos(wpos)) } } diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index d922944517..81888397dd 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -26,6 +26,19 @@ pub struct Colors { pub struct SpawnRules { pub trees: bool, pub max_warp: f32, + pub paths: bool, +} + +impl SpawnRules { + #[must_use] + pub fn combine(self, other: Self) -> Self { + // Should be commutative + Self { + trees: self.trees && other.trees, + max_warp: self.max_warp.min(other.max_warp), + paths: self.paths && other.paths, + } + } } impl Default for SpawnRules { @@ -33,6 +46,7 @@ impl Default for SpawnRules { Self { trees: true, max_warp: 1.0, + paths: true, } } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index ae1be53dc9..acd3d5dde2 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -60,13 +60,26 @@ impl Site { } pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { - let not_near_things = SQUARE_9.iter().all(|&rpos| { - self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32) - .is_empty() - }); + let tile_pos = self.wpos_tile_pos(wpos); + let max_warp = SQUARE_9 + .iter() + .filter_map(|rpos| { + let tile_pos = tile_pos + rpos; + if self.tiles.get(tile_pos).is_natural() { + None + } else { + let clamped = + wpos.clamped(self.tile_wpos(tile_pos), self.tile_wpos(tile_pos + 1) - 1); + Some(clamped.distance_squared(wpos) as f32) + } + }) + .min_by_key(|d2| *d2 as i32) + .map(|d2| d2.sqrt() as f32 / TILE_SIZE as f32) + .unwrap_or(1.0); SpawnRules { - trees: not_near_things, - max_warp: if not_near_things { 1.0 } else { 0.0 }, + trees: max_warp == 1.0, + max_warp, + paths: max_warp > std::f32::EPSILON, } } @@ -209,7 +222,7 @@ impl Site { pub fn make_plaza(&mut self, land: &Land, rng: &mut impl Rng) -> Id { let plaza_radius = rng.gen_range(1..4); - let plaza_dist = 10.0 + plaza_radius as f32 * 5.0; + let plaza_dist = 6.5 + plaza_radius as f32 * 4.0; let pos = attempt(32, || { self.plazas .choose(rng) @@ -288,7 +301,10 @@ impl Site { Spiral2d::new() .take((SEARCH_RADIUS * 2 + 1).pow(2) as usize) .for_each(|tile| { - if let Some(kind) = wpos_is_hazard(land, self.tile_wpos(tile)) { + if let Some(kind) = Spiral2d::new() + .take(9) + .find_map(|rpos| wpos_is_hazard(land, self.tile_center_wpos(tile) + rpos)) + { for &rpos in &SQUARE_4 { // `get_mut` doesn't increase generation bounds self.tiles @@ -369,7 +385,7 @@ impl Site { match *build_chance.choose_seeded(rng.gen()) { // House 1 => { - let size = (2.0 + rng.gen::().powf(5.0) * 1.5).round() as u32; + let size = (1.5 + rng.gen::().powf(5.0) * 1.0).round() as u32; if let Some((aabr, door_tile, door_dir)) = attempt(32, || { site.find_roadside_aabr( &mut rng, @@ -696,19 +712,24 @@ impl Site { if dist.map_or(false, |d| d <= 1.5) { let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32); - (-8..6).for_each(|z| { + let mut underground = true; + for z in -8..6 { canvas.map(Vec3::new(wpos2d.x, wpos2d.y, alt + z), |b| { - if z >= 0 { - if b.is_filled() { - Block::empty() + if b.kind() == BlockKind::Snow { + underground = false; + b.into_vacant() + } else if b.is_filled() { + if b.is_terrain() { + Block::new(BlockKind::Earth, Rgb::new(0x6A, 0x47, 0x24)) } else { - b.with_sprite(SpriteKind::Empty) + b } } else { - Block::new(BlockKind::Earth, Rgb::new(0x6A, 0x47, 0x24)) + underground = false; + b.into_vacant() } }) - }); + } } }); }, @@ -761,23 +782,32 @@ impl Site { if min_dist.is_some() { let alt = /*avg_hard_alt.map(|(sum, weight)| sum / weight).unwrap_or_else(||*/ canvas.col(wpos2d).map_or(0.0, |col| col.alt)/*)*/ as i32; - (-6..4).for_each(|z| canvas.map( - Vec3::new(wpos2d.x, wpos2d.y, alt + z), - |b| if z > 0 { - let sprite = if z == 1 && self.tile_wpos(tpos) == wpos2d && (tpos + tpos.yx() / 2) % 2 == Vec2::zero() { - SpriteKind::StreetLamp - } else { - SpriteKind::Empty - }; - if b.is_filled() { - Block::air(sprite) - } else { - b.with_sprite(sprite) - } - } else { - Block::new(BlockKind::Earth, Rgb::new(0x6A, 0x47, 0x24)) - }, - )); + let mut underground = true; + for z in -6..4 { + canvas.map( + Vec3::new(wpos2d.x, wpos2d.y, alt + z), + |b| { + let sprite = if underground && self.tile_wpos(tpos) == wpos2d && (tpos + tpos.yx() / 2) % 2 == Vec2::zero() { + SpriteKind::StreetLamp + } else { + SpriteKind::Empty + }; + if b.kind() == BlockKind::Snow { + underground = false; + b.into_vacant().with_sprite(sprite) + } else if b.is_filled() { + if b.is_terrain() { + Block::new(BlockKind::Earth, Rgb::new(0x6A, 0x47, 0x24)) + } else { + b + } + } else { + underground = false; + b.into_vacant().with_sprite(sprite) + } + }, + ); + } } let tile = self.wpos_tile(wpos2d); @@ -916,7 +946,7 @@ pub fn test_site() -> Site { Site::generate_city(&Land::empty(), &mut thread_rng fn wpos_is_hazard(land: &Land, wpos: Vec2) -> Option { if land - .get_chunk_at(wpos) + .get_chunk_wpos(wpos) .map_or(true, |c| c.river.near_water()) { Some(HazardKind::Water) diff --git a/world/src/site2/plot/dungeon.rs b/world/src/site2/plot/dungeon.rs index 571bade09e..5287570259 100644 --- a/world/src/site2/plot/dungeon.rs +++ b/world/src/site2/plot/dungeon.rs @@ -1423,7 +1423,7 @@ impl SiteStructure for Dungeon { } let biome = land - .get_chunk_at(self.origin) + .get_chunk_wpos(self.origin) .map_or(BiomeKind::Void, |c| c.get_biome()); let entrances = match biome { BiomeKind::Jungle => *JUNGLE, diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs index fe04481bda..c49ba1168f 100644 --- a/world/src/site2/plot/house.rs +++ b/world/src/site2/plot/house.rs @@ -856,6 +856,40 @@ impl Structure for House { painter.prim(Primitive::intersect(walls, windows)), Fill::Block(Block::air(SpriteKind::Window1).with_ori(2).unwrap()), ); + // Wall lamps + if i == 1 { + let mut torches_min = painter.prim(Primitive::Empty); + let mut torches_max = painter.prim(Primitive::Empty); + for y in self.tile_aabr.min.y..self.tile_aabr.max.y { + let pos = site + .tile_wpos(Vec2::new(self.tile_aabr.min.x, y)) + .with_z(alt + previous_height + 3) + + Vec3::new(-1, 0, 0); + let torch = painter.prim(Primitive::Aabb(Aabb { + min: pos, + max: pos + 1, + })); + torches_min = painter.prim(Primitive::union(torches_min, torch)); + + let pos = site + .tile_wpos(Vec2::new(self.tile_aabr.max.x, y + 1)) + .with_z(alt + previous_height + 3) + + Vec3::new(1, 0, 0); + let torch = painter.prim(Primitive::Aabb(Aabb { + min: pos, + max: pos + 1, + })); + torches_max = painter.prim(Primitive::union(torches_max, torch)); + } + painter.fill( + torches_min, + Fill::Block(Block::air(SpriteKind::WallLampSmall).with_ori(6).unwrap()), + ); + painter.fill( + torches_max, + Fill::Block(Block::air(SpriteKind::WallLampSmall).with_ori(2).unwrap()), + ); + } } // Windows y axis { @@ -886,6 +920,40 @@ impl Structure for House { painter.prim(Primitive::intersect(walls, windows)), Fill::Block(Block::air(SpriteKind::Window1).with_ori(0).unwrap()), ); + // Wall lamps + if i == 1 { + let mut torches_min = painter.prim(Primitive::Empty); + let mut torches_max = painter.prim(Primitive::Empty); + for x in self.tile_aabr.min.x..self.tile_aabr.max.x { + let pos = site + .tile_wpos(Vec2::new(x + 1, self.tile_aabr.min.y)) + .with_z(alt + previous_height + 3) + + Vec3::new(0, -1, 0); + let torch = painter.prim(Primitive::Aabb(Aabb { + min: pos, + max: pos + 1, + })); + torches_min = painter.prim(Primitive::union(torches_min, torch)); + + let pos = site + .tile_wpos(Vec2::new(x, self.tile_aabr.max.y)) + .with_z(alt + previous_height + 3) + + Vec3::new(0, 1, 0); + let torch = painter.prim(Primitive::Aabb(Aabb { + min: pos, + max: pos + 1, + })); + torches_max = painter.prim(Primitive::union(torches_max, torch)); + } + painter.fill( + torches_min, + Fill::Block(Block::air(SpriteKind::WallLampSmall).with_ori(0).unwrap()), + ); + painter.fill( + torches_max, + Fill::Block(Block::air(SpriteKind::WallLampSmall).with_ori(4).unwrap()), + ); + } } // Shed roof on negative overhangs diff --git a/world/src/site2/plot/workshop.rs b/world/src/site2/plot/workshop.rs index 35f75ad9bc..fb356bbbec 100644 --- a/world/src/site2/plot/workshop.rs +++ b/world/src/site2/plot/workshop.rs @@ -52,8 +52,8 @@ impl Structure for Workshop { painter .aabb(Aabb { - min: self.bounds.min.with_z(base), - max: self.bounds.max.with_z(roof), + min: (self.bounds.min + 2).with_z(base), + max: (self.bounds.max - 2).with_z(roof), }) .clear(); diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index 86019a2f4e..410a6be93b 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -216,6 +216,8 @@ impl Tile { pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty } + pub fn is_natural(&self) -> bool { matches!(self.kind, TileKind::Empty | TileKind::Hazard(_)) } + pub fn is_road(&self) -> bool { matches!(self.kind, TileKind::Plaza | TileKind::Road { .. }) } pub fn is_obstacle(&self) -> bool {