mod natural; use crate::{ column::{ColumnGen, ColumnSample}, util::{RandomField, Sampler, SmallCache}, CONFIG, }; use common::{ terrain::{structure::StructureBlock, Block, BlockKind, Structure}, vol::{ReadVol, Vox}, }; use std::ops::{Add, Div, Mul, Neg}; use vek::*; pub struct BlockGen<'a> { pub column_cache: SmallCache>>, pub column_gen: ColumnGen<'a>, } impl<'a> BlockGen<'a> { pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_cache: SmallCache::default(), column_gen, } } pub fn sample_column<'b>( column_gen: &ColumnGen<'a>, cache: &'b mut SmallCache>>, wpos: Vec2, ) -> Option<&'b ColumnSample<'a>> { cache.get(wpos, |wpos| column_gen.get(wpos)).as_ref() } fn get_cliff_height( column_gen: &ColumnGen<'a>, cache: &mut SmallCache>>, wpos: Vec2, close_cliffs: &[(Vec2, u32); 9], cliff_hill: f32, tolerance: f32, ) -> f32 { close_cliffs.iter().fold( 0.0f32, |max_height, (cliff_pos, seed)| match Self::sample_column(column_gen, cache, *cliff_pos) { Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => { let cliff_pos3d = Vec3::from(*cliff_pos); // Conservative range of height: [15.70, 49.33] let height = (RandomField::new(seed + 1).get(cliff_pos3d) % 64) as f32 // [0, 63] / (1 + 3 * [0.12, 1.32]) + 3 = // [0, 63] / (1 + [0.36, 3.96]) + 3 = // [0, 63] / [1.36, 4.96] + 3 = // [0, 63] / [1.36, 4.96] + 3 = // (height min) [0, 0] + 3 = [3, 3] // (height max) [12.70, 46.33] + 3 = [15.70, 49.33] / (1.0 + 3.0 * cliff_sample.chaos) + 3.0; // Conservative range of radius: [8, 47] let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; if cliff_sample .water_dist .map(|d| d > radius as f32) .unwrap_or(true) { max_height.max( if cliff_pos.map(|e| e as f32).distance_squared(wpos) < (radius as f32 + tolerance).powf(2.0) { cliff_sample.alt + height * (1.0 - cliff_sample.chaos) + cliff_hill } else { 0.0 }, ) } else { max_height } }, _ => max_height, }, ) } pub fn get_z_cache(&mut self, wpos: Vec2) -> Option> { let BlockGen { column_cache, column_gen, } = self; // Main sample let sample = column_gen.get(wpos)?; // Tree samples let mut structures = [None, None, None, None, None, None, None, None, None]; sample .close_structures .iter() .zip(structures.iter_mut()) .for_each(|(close_structure, structure)| { if let Some(st) = *close_structure { let st_sample = Self::sample_column(column_gen, column_cache, st.pos); if let Some(st_sample) = st_sample { let st_sample = st_sample.clone(); let st_info = match st.meta { None => natural::structure_gen( column_gen, column_cache, st.pos, st.seed, &st_sample, ), Some(meta) => Some(StructureInfo { pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32, seed: st.seed, meta, }), }; if let Some(st_info) = st_info { *structure = Some((st_info, st_sample)); } } } }); Some(ZCache { wpos, sample, structures, }) } pub fn get_with_z_cache( &mut self, wpos: Vec3, z_cache: Option<&ZCache>, only_structures: bool, ) -> Option { let BlockGen { column_cache, column_gen, } = self; let world = column_gen.sim; let sample = &z_cache?.sample; let &ColumnSample { alt, basement, chaos, water_level, warp_factor, surface_color, sub_surface_color, //tree_density, //forest_kind, //close_structures, cave_xy, cave_alt, marble, marble_small, rock, //cliffs, cliff_hill, close_cliffs, temp, humidity, stone_col, .. } = sample; let structures = &z_cache?.structures; let wposf = wpos.map(|e| e as f64); let (block, _height) = if !only_structures { let (_definitely_underground, height, on_cliff, basement_height, water_height) = if (wposf.z as f32) < alt - 64.0 * chaos { // Shortcut warping (true, alt, false, basement, water_level) } else { // Apply warping let warp = world .gen_ctx .warp_nz .get(wposf.div(24.0)) .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0)) .mul(16.0); let warp = Lerp::lerp(0.0, warp, warp_factor); let surface_height = alt + warp; let (height, on_cliff) = if (wposf.z as f32) < alt + warp - 10.0 { // Shortcut cliffs (surface_height, false) } else { let turb = Vec2::new( world.gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32, world.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( column_gen, column_cache, wpos_turb, &close_cliffs, cliff_hill, 0.0, ); ( surface_height.max(cliff_height), cliff_height > surface_height + 16.0, ) }; ( false, height, on_cliff, basement + height - alt, (if water_level <= alt { water_level + warp } else { water_level }), ) }; // Sample blocks // let stone_col = Rgb::new(195, 187, 201); // let dirt_col = Rgb::new(79, 67, 60); let _air = Block::empty(); // let stone = Block::new(2, stone_col); // let surface_stone = Block::new(1, Rgb::new(200, 220, 255)); // let dirt = Block::new(1, dirt_col); // let sand = Block::new(1, Rgb::new(180, 150, 50)); // let warm_stone = Block::new(1, Rgb::new(165, 165, 130)); let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); let block = if (wposf.z as f32) < height - grass_depth { let col = Lerp::lerp( sub_surface_color, stone_col.map(|e| e as f32 / 255.0), (height - grass_depth - wposf.z as f32) * 0.15, ) .map(|e| (e * 255.0) as u8); // Underground Some(Block::new(BlockKind::Normal, col)) } else if (wposf.z as f32) < height { let grass_factor = (wposf.z as f32 - (height - grass_depth)) .div(grass_depth) .powf(0.5); let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); // Surface Some(Block::new( if grass_factor > 0.7 { BlockKind::Grass } else { BlockKind::Normal }, col.map(|e| (e * 255.0) as u8), )) } else if (wposf.z as f32) < height + 0.9 && temp < CONFIG.desert_temp && (wposf.z as f32 > water_height + 3.0) && marble > 0.6 && marble_small > 0.55 && (marble * 3173.7).fract() < 0.6 && humidity > CONFIG.desert_hum { let treasures = [BlockKind::Chest, BlockKind::Velorite]; let flowers = [ BlockKind::BlueFlower, BlockKind::PinkFlower, BlockKind::PurpleFlower, BlockKind::RedFlower, BlockKind::WhiteFlower, BlockKind::YellowFlower, BlockKind::Sunflower, BlockKind::Mushroom, BlockKind::LeafyPlant, BlockKind::Blueberry, BlockKind::LingonBerry, BlockKind::Fern, ]; let grasses = [ BlockKind::LongGrass, BlockKind::MediumGrass, BlockKind::ShortGrass, ]; Some(Block::new( if on_cliff && (height * 1271.0).fract() < 0.015 { treasures[(height * 731.3) as usize % treasures.len()] } else if (height * 1271.0).fract() < 0.1 { flowers[(height * 0.2) as usize % flowers.len()] } else { grasses[(height * 103.3) as usize % grasses.len()] }, Rgb::broadcast(0), )) } else if (wposf.z as f32) < height + 0.9 && temp > CONFIG.desert_temp && (marble * 4423.5).fract() < 0.0005 { let large_cacti = [ BlockKind::LargeCactus, BlockKind::MedFlatCactus, BlockKind::Welwitch, ]; let small_cacti = [ BlockKind::BarrelCactus, BlockKind::RoundCactus, BlockKind::ShortCactus, BlockKind::ShortFlatCactus, BlockKind::DeadBush, ]; Some(Block::new( if (height * 1271.0).fract() < 0.5 { large_cacti[(height * 0.2) as usize % large_cacti.len()] } else { small_cacti[(height * 0.3) as usize % small_cacti.len()] }, Rgb::broadcast(0), )) } else { None } .or_else(|| { // Rocks if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { let field0 = RandomField::new(world.seed + 0); let field1 = RandomField::new(world.seed + 1); let field2 = RandomField::new(world.seed + 2); Some(Block::new( BlockKind::Rock, stone_col - Rgb::new( field0.get(wpos) as u8 % 16, field1.get(wpos) as u8 % 16, field2.get(wpos) as u8 % 16, ), )) } else { None } }) .and_then(|block| { // Caves // Underground let cave = cave_xy.powf(2.0) * (wposf.z as f32 - cave_alt) .div(40.0) .powf(4.0) .neg() .add(1.0) > 0.9993; if cave && wposf.z as f32 > water_height + 3.0 { None } else { Some(block) } }) .or_else(|| { // Water if (wposf.z as f32) < water_height { // Ocean Some(water) } else { None } }); (block, height) } else { (None, sample.alt) }; let block = structures .iter() .find_map(|st| { let (st, st_sample) = st.as_ref()?; st.get(wpos, st_sample) }) .or(block); Some(block.unwrap_or_else(Block::empty)) } } pub struct ZCache<'a> { wpos: Vec2, pub sample: ColumnSample<'a>, structures: [Option<(StructureInfo, ColumnSample<'a>)>; 9], } impl<'a> ZCache<'a> { pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) { let cave_depth = if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt { (self.sample.alt - self.sample.cave_alt + 8.0).max(0.0) } else { 0.0 }; let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth); let min = min - 4.0; let cliff = BlockGen::get_cliff_height( &block_gen.column_gen, &mut block_gen.column_cache, self.wpos.map(|e| e as f32), &self.sample.close_cliffs, self.sample.cliff_hill, 32.0, ); let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; let warp = self.sample.chaos * 32.0; let (structure_min, structure_max) = self .structures .iter() .filter_map(|st| st.as_ref()) .fold((0.0f32, 0.0f32), |(min, max), (st_info, _st_sample)| { let bounds = st_info.get_bounds(); let st_area = Aabr { min: Vec2::from(bounds.min), max: Vec2::from(bounds.max), }; if st_area.contains_point(self.wpos - st_info.pos) { (min.min(bounds.min.z as f32), max.max(bounds.max.z as f32)) } else { (min, max) } }); let ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0; let min = min + structure_min; let max = (ground_max + structure_max).max(self.sample.water_level + 2.0); let structures_only_min_z = ground_max.max(self.sample.water_level + 2.0); (min, structures_only_min_z, max) } } #[derive(Copy, Clone)] pub enum StructureMeta { Pyramid { height: i32, }, Volume { units: (Vec2, Vec2), volume: &'static Structure, }, } pub struct StructureInfo { pos: Vec3, seed: u32, meta: StructureMeta, } impl StructureInfo { fn get_bounds(&self) -> Aabb { match self.meta { StructureMeta::Pyramid { height } => { let base = 40; Aabb { min: Vec3::new(-base - height, -base - height, -base), max: Vec3::new(base + height, base + height, height), } }, StructureMeta::Volume { units, volume } => { let bounds = volume.get_bounds(); (Aabb { min: Vec3::from(units.0 * bounds.min.x + units.1 * bounds.min.y) + Vec3::unit_z() * bounds.min.z, max: Vec3::from(units.0 * bounds.max.x + units.1 * bounds.max.y) + Vec3::unit_z() * bounds.max.z, }) .made_valid() }, } } fn get(&self, wpos: Vec3, sample: &ColumnSample) -> Option { match self.meta { StructureMeta::Pyramid { height } => { if wpos.z - self.pos.z < height - Vec2::from(wpos - self.pos) .map(|e: i32| (e.abs() / 2) * 2) .reduce_max() { Some(Block::new(BlockKind::Dense, Rgb::new(203, 170, 146))) } else { None } }, StructureMeta::Volume { units, volume } => { let rpos = wpos - self.pos; let block_pos = Vec3::unit_z() * rpos.z + Vec3::from(units.0) * rpos.x + Vec3::from(units.1) * rpos.y; volume .get((block_pos * 128) / 128) // Scaling .ok() .and_then(|b| { block_from_structure( *b, block_pos, self.pos.into(), self.seed, sample, ) }) }, } } } pub fn block_from_structure( sblock: StructureBlock, pos: Vec3, structure_pos: Vec2, structure_seed: u32, sample: &ColumnSample, ) -> Option { let field = RandomField::new(structure_seed + 0); let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85 + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15; match sblock { StructureBlock::None => None, StructureBlock::Grass => Some(Block::new( BlockKind::Normal, sample.surface_color.map(|e| (e * 255.0) as u8), )), StructureBlock::TemperateLeaves => Some(Block::new( BlockKind::Leaves, Lerp::lerp( Rgb::new(0.0, 132.0, 94.0), Rgb::new(142.0, 181.0, 0.0), lerp, ) .map(|e| e as u8), )), StructureBlock::PineLeaves => Some(Block::new( BlockKind::Leaves, Lerp::lerp(Rgb::new(0.0, 60.0, 50.0), Rgb::new(30.0, 100.0, 10.0), lerp) .map(|e| e as u8), )), StructureBlock::PalmLeavesInner => Some(Block::new( BlockKind::Leaves, Lerp::lerp( Rgb::new(61.0, 166.0, 43.0), Rgb::new(29.0, 130.0, 32.0), lerp, ) .map(|e| e as u8), )), StructureBlock::PalmLeavesOuter => Some(Block::new( BlockKind::Leaves, Lerp::lerp( Rgb::new(62.0, 171.0, 38.0), Rgb::new(45.0, 171.0, 65.0), lerp, ) .map(|e| e as u8), )), StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::new(100, 150, 255))), StructureBlock::GreenSludge => Some(Block::new(BlockKind::Water, Rgb::new(30, 126, 23))), StructureBlock::Acacia => Some(Block::new( BlockKind::Leaves, Lerp::lerp( Rgb::new(15.0, 126.0, 50.0), Rgb::new(30.0, 180.0, 10.0), lerp, ) .map(|e| e as u8), )), StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 { Block::empty() } else { Block::new(BlockKind::Apple, Rgb::new(1, 1, 1)) }), StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { Block::empty() } else { Block::new(BlockKind::Coconut, Rgb::new(1, 1, 1)) }), StructureBlock::Chest => Some(if structure_seed % 10 < 7 { Block::empty() } else { Block::new(BlockKind::Chest, Rgb::new(1, 1, 1)) }), StructureBlock::Liana => Some(Block::new( BlockKind::Liana, Lerp::lerp( Rgb::new(0.0, 125.0, 107.0), Rgb::new(0.0, 155.0, 129.0), lerp, ) .map(|e| e as u8), )), StructureBlock::Mangrove => Some(Block::new( BlockKind::Leaves, Lerp::lerp(Rgb::new(32.0, 56.0, 22.0), Rgb::new(57.0, 69.0, 27.0), lerp) .map(|e| e as u8), )), StructureBlock::Hollow => Some(Block::empty()), StructureBlock::Normal(color) => { Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty()) }, } }