From 957ba5d218d66c72c86e8fb564be47ffcb6c5bc6 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sun, 27 Jun 2021 15:35:43 -0400 Subject: [PATCH] Use new dungeons in dungeon_voxel_export, remove old dungeon sampling function, and add more of world/examples to CI. --- .gitlab/CI/check.gitlab-ci.yml | 2 +- .../examples/chunk_compression_benchmarks.rs | 107 ++++--- world/examples/dungeon_voxel_export.rs | 72 ++++- world/examples/heightmap_visualization.rs | 15 +- world/src/canvas.rs | 38 +++ world/src/site2/gen.rs | 7 +- world/src/site2/mod.rs | 7 +- world/src/site2/plot.rs | 2 + world/src/site2/plot/dungeon.rs | 302 +----------------- 9 files changed, 176 insertions(+), 376 deletions(-) diff --git a/.gitlab/CI/check.gitlab-ci.yml b/.gitlab/CI/check.gitlab-ci.yml index 2f5b0a1490..c74f8d6aa1 100644 --- a/.gitlab/CI/check.gitlab-ci.yml +++ b/.gitlab/CI/check.gitlab-ci.yml @@ -6,7 +6,7 @@ code-quality: script: - ln -s /dockercache/target target - rm -r target/debug/incremental/* || echo "all good" # TMP FIX FOR 2021-03-22-nightly - - cargo clippy --all-targets --locked --features="bin_csv,bin_graphviz,bin_bot,asset_tweak" -- -D warnings + - cargo clippy --all-targets --locked --features="bin_compression,bin_csv,bin_graphviz,bin_bot,asset_tweak" -- -D warnings # Ensure that the veloren-voxygen default-publish feature builds as it excludes some default features - cargo clippy -p veloren-voxygen --locked --no-default-features --features="default-publish" -- -D warnings - cargo fmt --all -- --check diff --git a/world/examples/chunk_compression_benchmarks.rs b/world/examples/chunk_compression_benchmarks.rs index 2470a69c0a..9ac934b953 100644 --- a/world/examples/chunk_compression_benchmarks.rs +++ b/world/examples/chunk_compression_benchmarks.rs @@ -1,3 +1,4 @@ +#![allow(clippy::type_complexity)] use common::{ spiral::Spiral2d, terrain::{chonk::Chonk, Block, BlockKind, SpriteKind}, @@ -15,6 +16,7 @@ use common_net::msg::compression::{ use hashbrown::HashMap; use image::ImageBuffer; use num_traits::cast::FromPrimitive; +use rayon::ThreadPoolBuilder; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, @@ -56,8 +58,7 @@ fn do_deflate_rle(data: &[u8]) -> Vec { let mut encoder = DeflateEncoder::new(Vec::new(), CompressionOptions::rle()); encoder.write_all(data).expect("Write error!"); - let compressed_data = encoder.finish().expect("Failed to finish compression!"); - compressed_data + encoder.finish().expect("Failed to finish compression!") } // Separate function so that it shows up differently on the flamegraph @@ -66,8 +67,7 @@ fn do_deflate_flate2_zero(data: &[u8]) -> Vec { let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(0)); encoder.write_all(data).expect("Write error!"); - let compressed_data = encoder.finish().expect("Failed to finish compression!"); - compressed_data + encoder.finish().expect("Failed to finish compression!") } fn do_deflate_flate2(data: &[u8]) -> Vec { @@ -75,8 +75,7 @@ fn do_deflate_flate2(data: &[u8]) -> Vec { let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(LEVEL)); encoder.write_all(data).expect("Write error!"); - let compressed_data = encoder.finish().expect("Failed to finish compression!"); - compressed_data + encoder.finish().expect("Failed to finish compression!") } fn chonk_to_dyna( @@ -489,6 +488,7 @@ impl VoxelImageEncoding for MixedEncodingDenseSprites { } } +#[allow(clippy::many_single_char_names)] fn histogram_to_dictionary(histogram: &HashMap, usize>, dictionary: &mut Vec) { let mut tmp: Vec<(Vec, usize)> = histogram.iter().map(|(k, v)| (k.clone(), *v)).collect(); tmp.sort_by_key(|(_, count)| *count); @@ -507,13 +507,17 @@ fn histogram_to_dictionary(histogram: &HashMap, usize>, dictionary: &mut } fn main() { + let pool = ThreadPoolBuilder::new().build().unwrap(); common_frontend::init_stdout(None); println!("Loading world"); - let (world, index) = World::generate(59686, WorldOpts { - seed_elements: true, - world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), - ..WorldOpts::default() - }); + let (world, index) = World::generate( + 59686, + WorldOpts { + seed_elements: true, + world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + }, + &pool, + ); println!("Loaded world"); const HISTOGRAMS: bool = false; let mut histogram: HashMap, usize> = HashMap::new(); @@ -523,45 +527,45 @@ fn main() { let k = 32; let sz = world.sim().get_size(); - let mut sites = Vec::new(); - - sites.push(("center", sz / 2)); - sites.push(( - "dungeon", - world - .civs() - .sites() - .find(|s| s.is_dungeon()) - .map(|s| s.center.as_()) - .unwrap(), - )); - sites.push(( - "town", - world - .civs() - .sites() - .find(|s| s.is_settlement()) - .map(|s| s.center.as_()) - .unwrap(), - )); - sites.push(( - "castle", - world - .civs() - .sites() - .find(|s| s.is_castle()) - .map(|s| s.center.as_()) - .unwrap(), - )); - sites.push(( - "tree", - world - .civs() - .sites() - .find(|s| matches!(s.kind, SiteKind::Tree)) - .map(|s| s.center.as_()) - .unwrap(), - )); + let sites = vec![ + ("center", sz / 2), + ( + "dungeon", + world + .civs() + .sites() + .find(|s| s.is_dungeon()) + .map(|s| s.center.as_()) + .unwrap(), + ), + ( + "town", + world + .civs() + .sites() + .find(|s| s.is_settlement()) + .map(|s| s.center.as_()) + .unwrap(), + ), + ( + "castle", + world + .civs() + .sites() + .find(|s| s.is_castle()) + .map(|s| s.center.as_()) + .unwrap(), + ), + ( + "tree", + world + .civs() + .sites() + .find(|s| matches!(s.kind, SiteKind::Tree)) + .map(|s| s.center.as_()) + .unwrap(), + ), + ]; const SKIP_DEFLATE_2_5: bool = false; const SKIP_DYNA: bool = true; @@ -600,6 +604,7 @@ fn main() { let lz4chonk_pre = Instant::now(); let lz4_chonk = lz4_with_dictionary(&bincode::serialize(&chunk).unwrap(), &[]); let lz4chonk_post = Instant::now(); + #[allow(clippy::reversed_empty_ranges)] for _ in 0..ITERS { let _deflate0_chonk = do_deflate_flate2_zero(&bincode::serialize(&chunk).unwrap()); @@ -1024,7 +1029,7 @@ fn main() { for (name, value) in totals.iter() { println!("Average {}: {}", name, *value / count as f32); } - println!(""); + println!(); for (name, time) in total_timings.iter() { println!("Average {} nanos: {:02}", name, *time / count as f32); } diff --git a/world/examples/dungeon_voxel_export.rs b/world/examples/dungeon_voxel_export.rs index 426dc9a80b..e0971c351b 100644 --- a/world/examples/dungeon_voxel_export.rs +++ b/world/examples/dungeon_voxel_export.rs @@ -6,28 +6,65 @@ use std::{ type Result = std::io::Result<()>; use common::{ - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, SpriteKind}, vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, }; +use rayon::ThreadPoolBuilder; use vek::{Vec2, Vec3}; -use veloren_world::{index::Index, IndexOwned, Land}; +use veloren_world::{ + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + site2::{plot::PlotKind, Structure}, + CanvasInfo, Land, World, +}; /// This exports a dungeon (structure only, no entities or sprites) to a /// MagicaVoxel .vox file fn main() -> Result { + common_frontend::init_stdout(None); + let pool = ThreadPoolBuilder::new().build().unwrap(); + println!("Loading world"); + let (world, index) = World::generate( + 59686, + WorldOpts { + seed_elements: true, + world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + }, + &pool, + ); + println!("Loaded world"); let export_path = "dungeon.vox"; - let seed = 0; println!("Saving into {}", export_path); let mut volume = ExportVol::new(); - let index = IndexOwned::new(Index::new(seed)); - let dungeon = veloren_world::site2::plot::Dungeon::generate( - volume.size_xy().map(|p| p as i32 / 2), - &Land::empty(), - &mut rand::thread_rng(), - ); - dungeon.apply_to(index.as_index_ref(), Vec2::new(0, 0), |_| None, &mut volume); + let wpos = volume.size_xy().map(|p| p as i32 / 2); + let site = + veloren_world::site2::Site::generate_dungeon(&Land::empty(), &mut rand::thread_rng(), wpos); + CanvasInfo::with_mock_canvas_info(index.as_index_ref(), world.sim(), |canvas| { + for plot in site.plots() { + if let PlotKind::Dungeon(dungeon) = plot.kind() { + let (prim_tree, fills) = dungeon.render_collect(&site); + + for (prim, fill) in fills { + let aabb = fill.get_bounds(&prim_tree, prim); + + for x in aabb.min.x..aabb.max.x { + for y in aabb.min.y..aabb.max.y { + for z in aabb.min.z..aabb.max.z { + let pos = Vec3::new(x, y, z); + + if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas) + { + let _ = volume.set(pos, block); + } + } + } + } + } + } + } + }); + volume.write(&mut File::create(export_path)?) } @@ -157,7 +194,9 @@ impl ExportVol { write_chunk(file, "RGBA", &|file| { file.write_all(&[220, 220, 255, 0])?; // Air file.write_all(&[100, 100, 100, 0])?; // Rock - file.write_all(&[0; 4 * (256 - 2)]) + file.write_all(&[255, 0, 0, 0])?; // Sprite + file.write_all(&[255, 0, 255, 0])?; // GlowingRock + file.write_all(&[0; 4 * (256 - 4)]) })?; let chunks_end = file.stream_position()?; @@ -200,9 +239,16 @@ impl WriteVol for ExportVol { .entry(model_pos) .or_default() .extend_from_slice(&[rel_pos.x, rel_pos.y, rel_pos.z, match vox.kind() { - BlockKind::Air => 1, + BlockKind::Air => { + if !matches!(vox.get_sprite(), Some(SpriteKind::Empty)) { + 3 + } else { + 1 + } + }, BlockKind::Rock => 2, - _ => 3, + BlockKind::GlowingRock => 4, + _ => 5, }]); Ok(vox) } diff --git a/world/examples/heightmap_visualization.rs b/world/examples/heightmap_visualization.rs index 96ec9e3fcf..27b99708a2 100644 --- a/world/examples/heightmap_visualization.rs +++ b/world/examples/heightmap_visualization.rs @@ -2,6 +2,7 @@ use image::{ codecs::png::{CompressionType, FilterType, PngEncoder}, ImageBuffer, }; +use rayon::ThreadPoolBuilder; use std::{fs::File, io::Write}; use vek::*; use veloren_world::{ @@ -116,12 +117,16 @@ fn image_with_autorange [u8; 3], G: FnMut(u32, u32) -> f fn main() { common_frontend::init_stdout(None); + let pool = ThreadPoolBuilder::new().build().unwrap(); println!("Loading world"); - let (world, _index) = World::generate(59686, WorldOpts { - seed_elements: true, - world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), - ..WorldOpts::default() - }); + let (world, _index) = World::generate( + 59686, + WorldOpts { + seed_elements: true, + world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + }, + &pool, + ); println!("Loaded world"); let land = Land::from_sim(world.sim()); diff --git a/world/src/canvas.rs b/world/src/canvas.rs index f7a8b22300..12aba6ae19 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -49,6 +49,44 @@ impl<'a> CanvasInfo<'a> { pub fn chunks(&self) -> &'a WorldSim { self.chunks } pub fn land(&self) -> Land<'_> { Land::from_sim(self.chunks) } + + pub fn with_mock_canvas_info FnOnce(&CanvasInfo<'b>) -> A>( + index: IndexRef<'a>, + sim: &'a WorldSim, + f: F, + ) -> A { + let zcache_grid = Grid::populate_from(Vec2::broadcast(1), |_| None); + let sim_chunk = SimChunk { + chaos: 0.0, + alt: 0.0, + basement: 0.0, + water_alt: 0.0, + downhill: None, + flux: 0.0, + temp: 0.0, + humidity: 0.0, + rockiness: 0.0, + tree_density: 0.0, + forest_kind: crate::all::ForestKind::Palm, + spawn_rate: 0.0, + river: Default::default(), + surface_veg: 0.0, + sites: Vec::new(), + place: None, + path: Default::default(), + cave: Default::default(), + cliff_height: 0.0, + contains_waypoint: false, + }; + f(&CanvasInfo { + wpos: Vec2::broadcast(0), + column_grid: &zcache_grid, + column_grid_border: 0, + chunks: &sim, + index, + chunk: &sim_chunk, + }) + } } pub struct Canvas<'a> { diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index b2192df466..036db08171 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -157,7 +157,7 @@ impl Fill { tree: &Store, prim: Id, pos: Vec3, - canvas: &Canvas, + canvas_info: &crate::CanvasInfo, ) -> Option { if self.contains_at(tree, prim, pos) { match self { @@ -169,10 +169,9 @@ impl Fill { % *range as u32) as u8, )), Fill::Prefab(p, tr, seed) => p.get(pos - tr).ok().and_then(|sb| { - let info = canvas.info; - let col_sample = info.col(info.wpos)?; + let col_sample = canvas_info.col(canvas_info.wpos)?; block_from_structure( - canvas.index, + canvas_info.index, *sb, pos - tr, p.get_bounds().center().xy(), diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 73968d8934..5a5490e2ac 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -2,10 +2,10 @@ mod gen; pub mod plot; mod tile; -use self::{ +use self::tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE}; +pub use self::{ gen::{aabr_with_z, Fill, Primitive, Structure}, plot::{Plot, PlotKind}, - tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE}, }; use crate::{ site::SpawnRules, @@ -772,7 +772,8 @@ impl Site { for z in aabb.min.z..aabb.max.z { let pos = Vec3::new(x, y, z); - if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas) { + if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas.info) + { canvas.set(pos, block); } } diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index cbc79461e0..ff753f9070 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -24,6 +24,8 @@ impl Plot { b.expanded_to_contain_point(*t) }) } + + pub fn kind(&self) -> &PlotKind { &self.kind } } pub enum PlotKind { diff --git a/world/src/site2/plot/dungeon.rs b/world/src/site2/plot/dungeon.rs index 51e40e8c34..43cf4f6917 100644 --- a/world/src/site2/plot/dungeon.rs +++ b/world/src/site2/plot/dungeon.rs @@ -1,11 +1,9 @@ use super::SpawnRules; use crate::{ - block::block_from_structure, - column::ColumnSample, - site::{namegen::NameGen, BlockMask}, + site::namegen::NameGen, site2::{self, aabr_with_z, Fill, Primitive, Structure as SiteStructure}, util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, - IndexRef, Land, + Land, }; use common::{ @@ -15,7 +13,7 @@ use common::{ generation::{ChunkSupplement, EntityInfo}, store::{Id, Store}, terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize}, - vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol}, + vol::RectVolSize, }; use core::{f32, hash::BuildHasherDefault}; use fxhash::FxHasher64; @@ -128,79 +126,6 @@ impl Dungeon { pub fn difficulty(&self) -> u32 { self.difficulty } - pub fn apply_to<'a>( - &'a self, - index: IndexRef, - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, - vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), - ) { - lazy_static! { - pub static ref ENTRANCES: AssetHandle = - Structure::load_group("dungeon_entrances"); - } - - let entrances = ENTRANCES.read(); - let entrance = &entrances[self.seed as usize % entrances.len()]; - - for y in 0..vol.size_xy().y as i32 { - for x in 0..vol.size_xy().x as i32 { - let offs = Vec2::new(x, y); - - let wpos2d = wpos2d + offs; - let rpos = wpos2d - self.origin; - - // Apply the dungeon entrance - if let Some(col_sample) = get_column(offs) { - for z in entrance.get_bounds().min.z..entrance.get_bounds().max.z { - let wpos = Vec3::new(offs.x, offs.y, self.alt + z + ALT_OFFSET); - let spos = Vec3::new(rpos.x - TILE_SIZE / 2, rpos.y - TILE_SIZE / 2, z); - if let Some(block) = entrance - .get(spos) - .ok() - .copied() - .map(|sb| { - block_from_structure( - index, - sb, - spos, - self.origin, - self.seed, - col_sample, - // TODO: Take environment into account. - Block::air, - ) - }) - .unwrap_or(None) - { - let _ = vol.set(wpos, block); - } - } - }; - - // Apply the dungeon internals - let mut z = self.alt + ALT_OFFSET; - for floor in &self.floors { - z -= floor.total_depth(); - - let mut sampler = floor.col_sampler( - index, - rpos, - z, - // TODO: Take environment into account. - Block::air, - ); - - for rz in 0..floor.total_depth() { - if let Some(block) = sampler(rz).finish() { - let _ = vol.set(Vec3::new(offs.x, offs.y, z + rz), block); - } - } - } - } - } - } - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 pub fn apply_supplement<'a>( &'a self, @@ -695,10 +620,6 @@ impl Floor { fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth } - fn nearest_wall(&self, rpos: Vec2) -> Option> { - tilegrid_nearest_wall(&self.tiles, rpos) - } - // Find orientation of a position relative to another position #[allow(clippy::collapsible_else_if)] fn relative_ori(pos1: Vec2, pos2: Vec2) -> u8 { @@ -708,223 +629,6 @@ impl Floor { if pos1.x > pos2.x { 2 } else { 6 } } } - - #[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587 - fn col_sampler<'a>( - &'a self, - index: IndexRef<'a>, - pos: Vec2, - _floor_z: i32, - mut with_sprite: impl FnMut(SpriteKind) -> Block, - ) -> impl FnMut(i32) -> BlockMask + 'a { - let rpos = pos - self.tile_offset * TILE_SIZE; - let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); - let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - let rtile_pos = rpos - tile_center; - - let colors = &index.colors.site.dungeon; - - let vacant = BlockMask::new(with_sprite(SpriteKind::Empty), 1); - let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5); - - let make_spiral_staircase = - move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { - if (pos.xy().magnitude_squared() as f32) < inner_radius.powi(2) { - stone - } else if (pos.xy().magnitude_squared() as f32) < radius.powi(2) { - if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch - + pos.z as f32) - .rem_euclid(stretch) - < 1.5 - { - stone - } else { - vacant - } - } else { - BlockMask::nothing() - } - }; - let make_wall_staircase = - move |pos: Vec3, radius: f32, stretch: f32, height_limit: i32| { - if (pos.x.abs().max(pos.y.abs())) as f32 > 0.6 * radius && pos.z <= height_limit { - if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch - + pos.z as f32) - .rem_euclid(stretch) - < 1.0 - { - stone - } else { - vacant - } - } else { - vacant - } - }; - let make_staircase = move |kind: &StairsKind, - pos: Vec3, - radius: f32, - inner_radius: f32, - stretch: f32, - height_limit: i32| { - match kind { - StairsKind::Spiral => make_spiral_staircase(pos, radius, inner_radius, stretch), - StairsKind::WallSpiral => { - make_wall_staircase(pos, radius, stretch * 3.0, height_limit) - }, - } - }; - - let wall_thickness = 3.0; - let dist_to_wall = self - .nearest_wall(rpos) - .map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt()) - .unwrap_or(TILE_SIZE as f32); - let tunnel_dist = - 1.0 - (dist_to_wall - wall_thickness).max(0.0) / (TILE_SIZE as f32 - wall_thickness); - - let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.001) { - BlockMask::new( - with_sprite( - match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 30 { - 0 => SpriteKind::Apple, - 1 => SpriteKind::VeloriteFrag, - 2 => SpriteKind::Velorite, - 3..=8 => SpriteKind::Mushroom, - 9..=15 => SpriteKind::FireBowlGround, - _ => SpriteKind::ShortGrass, - }, - ), - 1, - ) - } else if let Some(Tile::Room(room)) | Some(Tile::DownStair(room)) = - self.tiles.get(tile_pos) - { - let room = &self.rooms[*room]; - if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) { - match room.difficulty { - 0 => BlockMask::new(with_sprite(SpriteKind::DungeonChest0), 1), - 1 => BlockMask::new(with_sprite(SpriteKind::DungeonChest1), 1), - 2 => BlockMask::new(with_sprite(SpriteKind::DungeonChest2), 1), - 3 => BlockMask::new(with_sprite(SpriteKind::DungeonChest3), 1), - 4 => BlockMask::new(with_sprite(SpriteKind::DungeonChest4), 1), - 5 => BlockMask::new(with_sprite(SpriteKind::DungeonChest5), 1), - _ => BlockMask::new(with_sprite(SpriteKind::Chest), 1), - } - } else { - vacant - } - } else { - vacant - }; - - let tunnel_height = if self.final_level { 16.0 } else { 8.0 }; - let pillar_thickness: i32 = 4; - - move |z| match self.tiles.get(tile_pos) { - Some(Tile::Solid) => BlockMask::nothing(), - Some(Tile::Tunnel) => { - let light_offset: i32 = 7; - if (dist_to_wall - wall_thickness) as i32 == 1 - && rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor() - && z == 1 - { - let ori = - Floor::relative_ori(rpos, self.nearest_wall(rpos).unwrap_or_default()); - let furniture = SpriteKind::WallSconce; - BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1) - } else if dist_to_wall >= wall_thickness - && (z as f32) < tunnel_height * (1.0 - tunnel_dist.powi(4)) - { - if z == 0 { floor_sprite } else { vacant } - } else { - BlockMask::nothing() - } - }, - Some(Tile::Room(room)) | Some(Tile::DownStair(room)) - if dist_to_wall < wall_thickness - || z as f32 - >= self.rooms[*room].height as f32 * (1.0 - tunnel_dist.powi(4)) => - { - BlockMask::nothing() - }, - - Some(Tile::Room(room)) | Some(Tile::DownStair(room)) - if self.rooms[*room] - .pillars - .map(|pillar_space| { - tile_pos - .map(|e| e.rem_euclid(pillar_space) == 0) - .reduce_and() - && rtile_pos.map(|e| e as f32).magnitude_squared() - < (pillar_thickness as f32 + 0.5).powi(2) - }) - .unwrap_or(false) => - { - if z == 1 && rtile_pos.product() == 0 && rtile_pos.sum().abs() == pillar_thickness { - let ori = Floor::relative_ori(rtile_pos, Vec2::zero()); - let furniture = SpriteKind::WallSconce; - BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1) - } else if z < self.rooms[*room].height - && rtile_pos.map(|e| e as f32).magnitude_squared() - > (pillar_thickness as f32 - 0.5).powi(2) - { - vacant - } else { - BlockMask::nothing() - } - } - - Some(Tile::Room(_)) => { - let light_offset = 7; - if z == 0 { - floor_sprite - } else if dist_to_wall as i32 == 4 - && rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor() - && z == 1 - { - let ori = Floor::relative_ori( - rpos, - self.nearest_wall(rpos).unwrap_or_else(Vec2::zero), - ); - let furniture = SpriteKind::WallSconce; - BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1) - } else { - vacant - } - }, - Some(Tile::DownStair(_)) => vacant, - Some(Tile::UpStair(room, kind)) => { - let inner_radius: f32 = 0.5; - let stretch = 9; - let block = make_staircase( - kind, - Vec3::new(rtile_pos.x, rtile_pos.y, z), - TILE_SIZE as f32 / 2.0, - inner_radius, - stretch as f32, - self.total_depth(), - ); - let furniture = SpriteKind::WallSconce; - let ori = Floor::relative_ori(Vec2::zero(), rtile_pos); - if z < self.rooms[*room].height { - block.resolve_with(vacant) - } else if z % stretch == 0 && rtile_pos.x == 0 && rtile_pos.y == -TILE_SIZE / 2 { - BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1) - } else { - make_staircase( - kind, - Vec3::new(rtile_pos.x, rtile_pos.y, z), - TILE_SIZE as f32 / 2.0, - inner_radius, - stretch as f32, - self.total_depth(), - ) - } - }, - None => BlockMask::nothing(), - } - } } fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {