use crate::{ column::{ColumnGen, ColumnSample}, util::{FastNoise, RandomField, RandomPerm, Sampler, SmallCache}, IndexRef, CONFIG, }; use common::{ calendar::{Calendar, CalendarEvent}, terrain::{ structure::{self, StructureBlock}, Block, BlockKind, SpriteKind, }, }; use core::ops::{Div, Mul, Range}; use serde::Deserialize; use vek::*; type Gradients = Vec>; #[derive(Deserialize)] pub struct Colors { // TODO(@Sharp): After the merge, construct enough infrastructure to make it convenient to // define mapping functions over the input; i.e. we should be able to interpret some fields as // defining App, Arg>, where Fun : (Context, Arg) → (S, Type). pub structure_blocks: structure::structure_block::PureCases>, } pub struct BlockGen<'a> { pub column_gen: ColumnGen<'a>, } impl<'a> BlockGen<'a> { pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_gen } } pub fn sample_column<'b>( column_gen: &ColumnGen<'a>, cache: &'b mut SmallCache>>, wpos: Vec2, index: IndexRef<'a>, calendar: Option<&'a Calendar>, ) -> Option<&'b ColumnSample<'a>> { cache .get(wpos, |wpos| column_gen.get((wpos, index, calendar))) .as_ref() } pub fn get_z_cache( &mut self, wpos: Vec2, index: IndexRef<'a>, calendar: Option<&'a Calendar>, ) -> Option> { let BlockGen { column_gen } = self; // Main sample let sample = column_gen.get((wpos, index, calendar))?; Some(ZCache { sample, calendar }) } pub fn get_with_z_cache(&mut self, wpos: Vec3, z_cache: Option<&ZCache>) -> Option { let BlockGen { column_gen } = self; let world = column_gen.sim; let z_cache = z_cache?; 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, marble, marble_mid, marble_small, rock, // temp, // humidity, stone_col, snow_cover, cliff_offset, cliff_height, // water_vel, ice_depth, .. } = sample; let wposf = wpos.map(|e| e as f64); let (_definitely_underground, height, basement_height, water_height) = if (wposf.z as f32) < alt - 64.0 * chaos { // Shortcut warping (true, alt, 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).powi(2)) .mul(16.0); let warp = Lerp::lerp(0.0, warp, warp_factor); let height = alt + warp; ( false, height, basement + height - alt, (if water_level <= alt { water_level + warp } else { water_level }), ) }; // Sample blocks let water = Block::new(BlockKind::Water, Rgb::zero()); let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); if (wposf.z as f32) < height - grass_depth { let stone_factor = (height - grass_depth - wposf.z as f32) * 0.15; let col = Lerp::lerp( sub_surface_color, stone_col.map(|e| e as f32 / 255.0), stone_factor, ) .map(|e| (e * 255.0) as u8); if stone_factor >= 0.5 { if wposf.z as f32 > height - cliff_offset.max(0.0) { if cliff_offset.max(0.0) > cliff_height - (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5 + 0.5) * (height - grass_depth - wposf.z as f32) .mul(0.25) .clamped(0.0, 8.0) { Some(Block::empty()) } else { let col = Lerp::lerp( col.map(|e| e as f32), col.map(|e| e as f32) * 0.7, (wposf.z as f32 - basement * 0.3).div(2.0).sin() * 0.5 + 0.5, ) .map(|e| e as u8); Some(Block::new(BlockKind::Rock, col)) } } else { Some(Block::new(BlockKind::Rock, col)) } } else { Some(Block::new(BlockKind::Earth, col)) } } else if wposf.z as i32 <= height as i32 { let grass_factor = (wposf.z as f32 - (height - grass_depth)) .div(grass_depth) .sqrt(); // Surface Some(if water_level > height.ceil() { Block::new( BlockKind::Sand, sub_surface_color.map(|e| (e * 255.0) as u8), ) } else { let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); if grass_factor < 0.7 { Block::new(BlockKind::Earth, col.map(|e| (e * 255.0) as u8)) } else if snow_cover { //if temp < CONFIG.snow_temp + 0.031 { Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8)) } else { Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8)) } }) } else { None } .or_else(|| { // Rocks if (height + 2.5 - wposf.z as f32).div(7.5).abs().powi(2) < rock { #[allow(clippy::identity_op)] 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::WeakRock, stone_col.map2( Rgb::new( field0.get(wpos) as u8 % 16, field1.get(wpos) as u8 % 16, field2.get(wpos) as u8 % 16, ), |stone, x| stone.saturating_sub(x), ), )) } else { None } }) .or_else(|| { let over_water = height < water_height; // Water if over_water && (wposf.z as f32 - water_height).abs() < ice_depth { // TODO: Ice block Some(Block::new(BlockKind::WeakRock, CONFIG.ice_color)) } else if (wposf.z as f32) < water_height { // Ocean Some(water) } else { None } }) } } pub struct ZCache<'a> { pub sample: ColumnSample<'a>, pub calendar: Option<&'a Calendar>, } impl<'a> ZCache<'a> { pub fn get_z_limits(&self) -> (f32, f32) { let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0) - self.sample.cliff_offset.max(0.0); let min = min - 4.0; let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; let warp = self.sample.chaos * 32.0; let ground_max = self.sample.alt + warp + rocks + 2.0; let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth); (min, max) } } pub fn block_from_structure( index: IndexRef, sblock: StructureBlock, pos: Vec3, structure_pos: Vec2, structure_seed: u32, sample: &ColumnSample, mut with_sprite: impl FnMut(SpriteKind) -> Block, calendar: Option<&Calendar>, ) -> Option { let field = RandomField::new(structure_seed); let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.8 + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.2; match sblock { StructureBlock::None => None, StructureBlock::Hollow => Some(Block::air(SpriteKind::Empty)), StructureBlock::Grass => Some(Block::new( BlockKind::Grass, sample.surface_color.map(|e| (e * 255.0) as u8), )), StructureBlock::Normal(color) => Some(Block::new(BlockKind::Misc, color)), StructureBlock::Filled(kind, color) => Some(Block::new(kind, color)), StructureBlock::Sprite(kind) => Some(with_sprite(kind).into_vacant().with_sprite(kind)), StructureBlock::Water => Some(Block::water(SpriteKind::Empty)), // TODO: If/when liquid supports other colors again, revisit this. StructureBlock::GreenSludge => Some(Block::water(SpriteKind::Empty)), // None of these BlockKinds has an orientation, so we just use zero for the other color // bits. StructureBlock::Liana => Some(with_sprite(SpriteKind::Liana)), StructureBlock::Fruit => { if field.get(pos + structure_pos) % 24 == 0 { Some(with_sprite(SpriteKind::Beehive)) } else if field.get(pos + structure_pos + 1) % 3 == 0 { Some(with_sprite(SpriteKind::Apple)) } else { None } }, StructureBlock::Coconut => { if field.get(pos + structure_pos) % 3 > 0 { None } else { Some(with_sprite(SpriteKind::Coconut)) } }, StructureBlock::Chest => { let old_block = with_sprite(SpriteKind::Empty); let block = if old_block.is_fluid() { old_block } else { Block::air(SpriteKind::Empty) }; if field.chance(pos + structure_pos, 0.5) { Some(block) } else { Some(block.with_sprite(SpriteKind::Chest)) } }, StructureBlock::Log => Some(Block::new(BlockKind::Wood, Rgb::new(60, 30, 0))), // We interpolate all these BlockKinds as needed. StructureBlock::TemperateLeaves | StructureBlock::PineLeaves | StructureBlock::PalmLeavesInner | StructureBlock::PalmLeavesOuter | StructureBlock::Acacia | StructureBlock::Mangrove | StructureBlock::Chestnut | StructureBlock::Baobab => { let ranges = sblock .elim_case_pure(&index.colors.block.structure_blocks) .as_ref() .map(Vec::as_slice) .unwrap_or(&[]); let range = if ranges.is_empty() { None } else { ranges.get( RandomPerm::new(structure_seed).get(structure_seed) as usize % ranges.len(), ) }; range.map(|range| { if calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas)) && field.chance(pos + structure_pos, 0.025) { Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)) } else { Block::new( BlockKind::Leaves, Rgb::::lerp( Rgb::::from(range.start).map(f32::from), Rgb::::from(range.end).map(f32::from), lerp, ) .map(|e| e as u8), ) } }) }, StructureBlock::BirchWood => { let wpos = pos + structure_pos; if field.chance( (wpos + Vec3::new(wpos.z, wpos.z, 0) / 2) / Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1), 0.25, ) && wpos.z % 2 == 0 { Some(Block::new(BlockKind::Wood, Rgb::new(70, 35, 25))) } else { Some(Block::new(BlockKind::Wood, Rgb::new(220, 170, 160))) } }, } }