From e3ea662668253c2ce311f9a2b00c73353a468349 Mon Sep 17 00:00:00 2001 From: popertots Date: Wed, 7 May 2025 20:13:29 +0100 Subject: [PATCH] fake fog, better parallelisation --- Cargo.lock | 20 ++++---- src/camera.rs | 6 ++- src/citizen.rs | 133 ++++++++++++++++++++++++++++++------------------- src/tilemap.rs | 20 +++++--- src/tiles.rs | 107 ++++++++++++++++++++++----------------- 5 files changed, 170 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f3a914..cec7581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,9 +2761,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -2773,7 +2773,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", - "redox_syscall 0.5.11", + "redox_syscall 0.5.12", ] [[package]] @@ -3461,7 +3461,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.11", + "redox_syscall 0.5.12", "smallvec", "windows-targets 0.52.6", ] @@ -3876,9 +3876,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -4011,9 +4011,9 @@ dependencies = [ [[package]] name = "ruzstd" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c581601827da5c717bfae77d7b187e54293d23d8fb6b700b4b5e9b5828a13cc3" +checksum = "3640bec8aad418d7d03c72ea2de10d5c646a598f9883c7babc160d91e3c1b26c" dependencies = [ "twox-hash", ] @@ -5404,9 +5404,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/src/camera.rs b/src/camera.rs index 5ba3b3e..7c42d15 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,6 +1,8 @@ use bevy::input::keyboard::KeyCode; use bevy::prelude::*; +use crate::constants::TILE_SIZE; + #[derive(Component)] pub struct PanningCamera { pub pan_speed: f32, @@ -39,7 +41,7 @@ pub fn camera_movement( pub fn spawn_panning_camera(mut commands: Commands) { commands.spawn(( Camera2d, - Transform::from_xyz(0., 0., 10.), - PanningCamera { pan_speed: 5.0 }, + Transform::from_xyz(0., 0., 10. * TILE_SIZE), + PanningCamera { pan_speed: 15.0 }, )); } diff --git a/src/citizen.rs b/src/citizen.rs index 91a2d70..cd71d57 100644 --- a/src/citizen.rs +++ b/src/citizen.rs @@ -17,10 +17,12 @@ impl Citizen { pub fn new(asset_server: &Res, position: Vec3) -> Self { Citizen { ambulatory: Ambulatory { - speed: TILE_SIZE, + walk_speed: 2., + run_speed: 6., target: None, current_path: None, path_index: 0, + step_recovery: 0, }, sprite: Sprite { image: asset_server.load("dorf.png"), @@ -42,10 +44,12 @@ use std::{ #[derive(Component)] pub struct Ambulatory { - pub speed: f32, + pub walk_speed: f32, + pub run_speed: f32, pub current_path: Option>, pub path_index: usize, pub target: Option, + pub step_recovery: u32, } #[derive(Clone, Eq, PartialEq, Debug)] @@ -131,65 +135,92 @@ pub fn update_citizen_wandering_targets( } pub fn citizen_movement( - mut query: Query<(&mut Ambulatory, &mut Transform, &mut Visibility)>, + mut query: Query<( + &mut Ambulatory, + &mut Transform, + &mut Visibility, + &mut Sprite, + )>, tilemap: Res, z_index: Res, ) { - for (mut ambulatory, mut transform, mut visibility) in query.iter_mut() { - let current_pos = transform.translation; - let below_pos = Vec3::new( - current_pos.x, - current_pos.y, - current_pos.z - TILE_SIZE - 0.1, - ); + query.par_iter_mut().for_each( + |(mut ambulatory, mut transform, mut visibility, mut sprite)| { + let current_pos = transform.translation; + let below_pos = Vec3::new( + current_pos.x, + current_pos.y, + current_pos.z - TILE_SIZE - 0.1, + ); - if !is_standable_tile(&tilemap, current_pos.as_ivec3()) { - transform.translation = below_pos + Vec3::new(0.0, 0.0, 0.1); - continue; - } - - if let Some(target) = ambulatory.target { - // Calculate path if needed - if ambulatory.current_path.is_none() { - ambulatory.current_path = Some(calculate_path( - &tilemap, - transform.translation.as_ivec3(), - target.as_ivec3() - ivec3(0, 0, 1), - )); - ambulatory.path_index = 0; + if !is_standable_tile(&tilemap, current_pos.as_ivec3()) { + transform.translation = below_pos + Vec3::new(0.0, 0.0, 0.1); + return; } - // Follow the current path - if let Some(path) = &ambulatory.current_path { - if ambulatory.path_index < path.len() { - let next_point = path[ambulatory.path_index]; - - let direction = (next_point - transform.translation).normalize(); - transform.translation = next_point; - - // Update sprite direction (only for x movement) - if direction.x > 0.0 { - transform.scale.x = PIXEL_RATIO; - } else if direction.x < 0.0 { - transform.scale.x = -PIXEL_RATIO; - } - if transform.translation.z.round() / TILE_SIZE <= z_index.0 + 1.0 { - *visibility = Visibility::Visible; + if let Some(target) = ambulatory.target { + // Calculate path if needed + if ambulatory.current_path.is_none() { + ambulatory.current_path = Some(calculate_path( + &tilemap, + transform.translation.as_ivec3(), + target.as_ivec3() - ivec3(0, 0, 1), + )); + ambulatory.path_index = 0; + } + if ambulatory.walk_speed > 0. { + if ambulatory.step_recovery <= ambulatory.walk_speed as u32 { + ambulatory.step_recovery += 1; + return; } else { - *visibility = Visibility::Hidden; + ambulatory.step_recovery = 0; } + } - // Check if we've reached the next point - if transform.translation.distance(next_point) < ambulatory.speed { - ambulatory.path_index += 1; + // Follow the current path + if let Some(path) = &ambulatory.current_path { + if ambulatory.path_index < path.len() { + let next_point = path[ambulatory.path_index]; + + let direction = (next_point - transform.translation).normalize(); + transform.translation = next_point; + + // Update sprite direction (only for x movement) + if direction.x > 0.0 { + transform.scale.x = PIXEL_RATIO; + } else if direction.x < 0.0 { + transform.scale.x = -PIXEL_RATIO; + } + if (z_index.0 - transform.translation.z / TILE_SIZE) / 8.0 > 1.0 { + *visibility = Visibility::Hidden; + } else { + if transform.translation.z / TILE_SIZE <= z_index.0 + 1.0 { + *visibility = Visibility::Visible; + let saturation = + ((z_index.0 - transform.translation.z / TILE_SIZE) / 8.0) + .clamp(0.0, 1.0); + + sprite.color = + Color::hsv(194.7, saturation, 1.0 - (saturation / 2.0)); + + sprite.color.set_alpha(1.0 - saturation); + } else { + *visibility = Visibility::Hidden; + } + } + + // Check if we've reached the next point + if transform.translation.distance(next_point) < TILE_SIZE { + ambulatory.path_index += 1; + } + } else { + ambulatory.current_path = None; + ambulatory.target = None; } - } else { - ambulatory.current_path = None; - ambulatory.target = None; } } - } - } + }, + ); } fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool { @@ -370,12 +401,10 @@ fn reconstruct_path(came_from: HashMap, mut current: IVec3) -> Vec } pub fn spawn_citizens(mut commands: Commands, asset_server: Res) { - println!("spawning citizens"); - let mut rng = rand::rng(); // Spawn a handful of citizens - for _ in 0..10 { + for _ in 0..100 { let x: f32 = rng.random_range(-8.0..8.0); let y: f32 = rng.random_range(-8.0..8.0); let mut position = Vec3::new(x.round(), y.round(), 30.0) * TILE_SIZE; diff --git a/src/tilemap.rs b/src/tilemap.rs index 7315a49..f661180 100644 --- a/src/tilemap.rs +++ b/src/tilemap.rs @@ -1,3 +1,5 @@ +use std::time::Instant; + use crate::constants::{ITILE_SIZE, TILE_SIZE}; use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileMap}; use crate::tiles::{ @@ -9,7 +11,7 @@ use noise::{NoiseFn, Perlin}; pub const CHUNK_SIZE: i32 = 8; -pub const Z_BELOW: f32 = 10.0; +pub const Z_BELOW: f32 = 3.0; pub const Z_ABOVE: f32 = 5.0; pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW; // MAX 255 DO NOT EXCEED @@ -46,7 +48,7 @@ pub fn handle_tile_occlusion_updates( )>, mut cwss: ResMut, ) { - println!("updating tile occlusion"); + let start = Instant::now(); query_set .p0() .par_iter_mut() @@ -77,6 +79,7 @@ pub fn handle_tile_occlusion_updates( } } cwss.state = TerrainSpriteState::WaitingForRender; + println!("Tile occlusion updated in {:.2?}", start.elapsed()); } pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { @@ -159,10 +162,11 @@ fn generate_chunks_from_algo( mut chunk_map: ResMut, mut tilemap: ResMut, ) { - let cave_noise = Perlin::new(0); + let is_empty = events.is_empty(); + let start = Instant::now(); + let cave_noise = Perlin::new(0); for event in events.read() { - println!("Loading chunk {}", event.chunk_position); let chunk_pos = event.chunk_position; if chunk_map.loaded_chunks.contains_key(&chunk_pos) { continue; @@ -273,12 +277,14 @@ fn generate_chunks_from_algo( } } } + if !is_empty { + println!("Chunks loaded in {:.2?}", start.elapsed()); + } } fn setup_initial_chunks(mut event_writer: EventWriter) { - println!("setup_initial_chunks"); - for x in -3..=3 { - for y in -2..=2 { + for x in -10..=10 { + for y in -10..=10 { event_writer.write(LoadChunkEvent { chunk_position: IVec2::new(x, y), }); diff --git a/src/tiles.rs b/src/tiles.rs index c0a15d7..59dbfe0 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -113,6 +113,7 @@ impl Default for QuiltCache { } } +// here be dragons :( pub fn build_quilted_terrain_sprites( query: Query<(&FloorTile, &Transform)>, mut commands: Commands, @@ -155,9 +156,6 @@ pub fn build_quilted_terrain_sprites( continue; } - // Snap min/max values to tile grid - accounting for half tile on each edge - // We need to extend the boundaries by half a tile in each direction to ensure full coverage - // Don't ask, dragons. let min_x_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.x).reduce(f32::min).unwrap() - TILE_SIZE / 2.0) / TILE_SIZE) @@ -207,23 +205,25 @@ pub fn build_quilted_terrain_sprites( let target_y = tile_y * TILE_PIXELS; let mut data: Vec<&[u8]> = vec![]; - if let Some(source_texture) = images.get(texture) { - if let Some(_data) = &source_texture.data { - // replace this one texture push with loop - // if the texture contains any transparent or semi-transparent pixels we should also add the tile below it (z-1) + let mut base_texture: &Image = &Default::default(); + + if let Some(_base_texture) = images.get(texture) { + if let Some(_data) = &_base_texture.data { data.push(_data); + base_texture = _base_texture; } - blit_texture( - data, // tile - &mut texture_data, // terrain - source_texture.size().x as u32, - source_texture.size().y as u32, - width_px, - height_px, - target_x, - target_y, - ); } + + blit_texture_with_alpha( + data, // tile + &mut texture_data, // terrain + base_texture.size().x as u32, + base_texture.size().y as u32, + width_px, + height_px, + target_x, + target_y, + ); } let quilted_texture = Image::new_fill( @@ -248,9 +248,9 @@ pub fn build_quilted_terrain_sprites( ..Default::default() }, Transform::from_xyz( - center_x + TILE_SIZE / 2.0, - center_y + TILE_SIZE / 2.0, - (*z_index as f32 - Z_BELOW) * TILE_SIZE, + center_x - TILE_SIZE / 2.0, + center_y - TILE_SIZE / 2.0, + -10.0 * TILE_SIZE, ) .with_scale(Vec3::splat(PIXEL_RATIO)), Visibility::Hidden, @@ -267,7 +267,7 @@ pub fn build_quilted_terrain_sprites( } // Helper function to blit a texture onto another texture -fn blit_texture( +fn blit_texture_with_alpha( sources: Vec<&[u8]>, target: &mut [u8], source_width: u32, @@ -277,35 +277,52 @@ fn blit_texture( offset_x: u32, offset_y: u32, ) { - for y in 0..source_height { - if y + offset_y >= target_height { - continue; - } - for x in 0..source_width { - if x + offset_x >= target_width { + for (_, source_data) in sources.iter().rev().enumerate() { + for y in 0..source_height { + if y + offset_y >= target_height { continue; } - let source_idx = ((y * source_width) + x) as usize * 4; - let target_idx = (((y + offset_y) * target_width) + (x + offset_x)) as usize * 4; + for x in 0..source_width { + if x + offset_x >= target_width { + continue; + } - // New colour - let mut r: u64 = 0; - let mut g: u64 = 0; - let mut b: u64 = 0; + let source_pixel_idx = ((y * source_width) + x) as usize * 4; + let target_pixel_idx = + (((y + offset_y) * target_width) + (x + offset_x)) as usize * 4; - for source in sources.iter() { - r += source[source_idx] as u64; - g += source[source_idx + 1] as u64; - b += source[source_idx + 2] as u64; + let src_a = source_data[source_pixel_idx + 3]; + + if src_a == 0 { + continue; + } + + let src_r = source_data[source_pixel_idx]; + let src_g = source_data[source_pixel_idx + 1]; + let src_b = source_data[source_pixel_idx + 2]; + + if src_a == 255 { + target[target_pixel_idx] = src_r; + target[target_pixel_idx + 1] = src_g; + target[target_pixel_idx + 2] = src_b; + target[target_pixel_idx + 3] = 255; + } else { + let dst_r = target[target_pixel_idx]; + let dst_g = target[target_pixel_idx + 1]; + let dst_b = target[target_pixel_idx + 2]; + + let alpha_factor = src_a as f32 / 255.0; + let inv_alpha = 1.0 - alpha_factor; + + target[target_pixel_idx] = + (src_r as f32 * alpha_factor + dst_r as f32 * inv_alpha) as u8; + target[target_pixel_idx + 1] = + (src_g as f32 * alpha_factor + dst_g as f32 * inv_alpha) as u8; + target[target_pixel_idx + 2] = + (src_b as f32 * alpha_factor + dst_b as f32 * inv_alpha) as u8; + target[target_pixel_idx + 3] = 255; + } } - r /= sources.len() as u64; - g /= sources.len() as u64; - b /= sources.len() as u64; - - target[target_idx] = r as u8; - target[target_idx + 1] = g as u8; - target[target_idx + 2] = b as u8; - target[target_idx + 3] = 255; } } }