From 6f4de57f1b9ffb9b07a5090e9857d76c73ffc2ef Mon Sep 17 00:00:00 2001 From: popertots Date: Tue, 6 May 2025 22:31:31 +0100 Subject: [PATCH] average composite on terrain sprites --- src/citizen.rs | 32 +--------- src/constants.rs | 5 +- src/main.rs | 2 +- src/tile.rs | 4 +- src/tilemap.rs | 12 ++-- src/tiles.rs | 153 +++++++++++++++++++++++++---------------------- 6 files changed, 93 insertions(+), 115 deletions(-) diff --git a/src/citizen.rs b/src/citizen.rs index 7ea253d..91a2d70 100644 --- a/src/citizen.rs +++ b/src/citizen.rs @@ -315,9 +315,6 @@ fn calculate_path(tilemap: &TileMap, start: IVec3, goal: IVec3) -> Vec { open_set.push(neighbor_node); in_open_set.insert(neighbor_pos); } else { - // For nodes already in the open set, we need to update their priority - // This is tricky with a binary heap - one approach is to add a new entry - // The old one will still be there but will be ignored when we check the closed set let neighbor_node = PathNode { position: neighbor_pos, f_score: f, @@ -342,8 +339,6 @@ fn octile_distance_3d(a: IVec3, b: IVec3) -> i32 { let cost2 = 14; // 2D Diagonal let cost3 = 17; // 3D Diagonal - // The heuristic calculation sorts the dimensions implicitly - // It calculates the minimum number of diagonal steps needed let mut diffs = [dx, dy, dz]; diffs.sort_unstable(); // Sorts ascending: [dmin, dmid, dmax] @@ -375,6 +370,8 @@ 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 @@ -387,28 +384,3 @@ pub fn spawn_citizens(mut commands: Commands, asset_server: Res) { commands.spawn(Citizen::new(&asset_server, position)); } } - -// pub fn check_citizen_positions_for_chunks( -// query: Query<&Transform, With>, -// chunk_map: Res, -// mut event_writer: EventWriter, -// ) { -// for transform in query.iter() { -// let position = transform.translation; - -// // Convert citizen position to chunk coordinates -// let chunk_x = -// (position.x / (tilemap::CHUNK_SIZE as f32 * constants::TILE_SIZE)).floor() as i32; -// let chunk_y = -// (position.y / (tilemap::CHUNK_SIZE as f32 * constants::TILE_SIZE)).floor() as i32; - -// let chunk_pos = IVec2::new(chunk_x, chunk_y); - -// // // Load chunk if not already loaded -// // if !chunk_map.loaded_chunks.contains_key(&chunk_pos) { -// // event_writer.send(tilemap::LoadChunkEvent { -// // chunk_position: chunk_pos, -// // }); -// // } -// } -// } diff --git a/src/constants.rs b/src/constants.rs index 002a045..b55ebb2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,4 @@ pub const PIXEL_RATIO: f32 = 2.0; -pub const TILE_PIXELS: f32 = 16.0; -pub const TILE_SIZE: f32 = TILE_PIXELS * PIXEL_RATIO; +pub const TILE_PIXELS: u32 = 16; +pub const TILE_SIZE: f32 = TILE_PIXELS as f32 * PIXEL_RATIO; pub const ITILE_SIZE: i32 = TILE_SIZE as i32; -pub const UTILE_SIZE: u32 = TILE_SIZE as u32; diff --git a/src/main.rs b/src/main.rs index 40fbe97..4705c43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ fn main() { }) .set(ImagePlugin::default_nearest()), ) - .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0))) + .insert_resource(ClearColor(Color::srgb(0., 0., 0.))) .add_plugins(tilemap::TilemapPlugin) .add_plugins(citizen::PathfindingPlugin) .add_systems( diff --git a/src/tile.rs b/src/tile.rs index c1f4a01..7193ef5 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -76,7 +76,6 @@ pub struct TileMap { pub fixture_tiles: HashMap, } -// This system updates sprite visibility based on current z-index pub fn update_tile_visibility( z_index: Res, mut query: Query<(&tiles::TerrainSprite, &mut Visibility)>, @@ -85,10 +84,9 @@ pub fn update_tile_visibility( let current_z = z_index.0 as isize; - // Update visibility for all terrain sprites for (terrain_sprite, mut visibility) in query.iter_mut() { *visibility = - if terrain_sprite.z_index <= ((current_z + tilemap::Z_BELOW as isize) as usize) { + if terrain_sprite.z_index == ((current_z + tilemap::Z_BELOW as isize) as usize) { Visibility::Visible } else { Visibility::Hidden diff --git a/src/tilemap.rs b/src/tilemap.rs index 75cde26..7315a49 100644 --- a/src/tilemap.rs +++ b/src/tilemap.rs @@ -46,6 +46,7 @@ pub fn handle_tile_occlusion_updates( )>, mut cwss: ResMut, ) { + println!("updating tile occlusion"); query_set .p0() .par_iter_mut() @@ -91,7 +92,8 @@ pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { let mut is_occluded = false; - 'vertical_check: for z_offset in 1..15 { + let v_check_height: i32 = Z_TOTAL as i32 - (camera_z / ITILE_SIZE) + Z_BELOW as i32 + 1; + 'vertical_check: for z_offset in 1..v_check_height { let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * ITILE_SIZE)); if above_pos.z <= camera_z { if let Some(&(_, opaque, _, _, _)) = tilemap.floor_tiles.get(&above_pos) { @@ -194,13 +196,13 @@ fn generate_chunks_from_algo( ); let pos_ivec = position.as_ivec3(); - if z < -8 { + if z < -5 { let noise_value = cave_noise.get([ world_x as f64 * 0.05, world_y as f64 * 0.05, - z as f64 * 0.05, + z as f64 * (0.15 * -z as f64), ]); - if noise_value < -0.5 { + if noise_value < -0.65 { FloorTilePrefab::air(position).spawn(&mut commands); tilemap .floor_tiles @@ -275,7 +277,7 @@ fn generate_chunks_from_algo( fn setup_initial_chunks(mut event_writer: EventWriter) { println!("setup_initial_chunks"); - for x in -2..=2 { + for x in -3..=3 { for y in -2..=2 { event_writer.write(LoadChunkEvent { chunk_position: IVec2::new(x, y), diff --git a/src/tiles.rs b/src/tiles.rs index c0723c7..c0a15d7 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -74,7 +74,6 @@ pub fn initialize_textures(mut commands: Commands, asset_server: Res, - // Tracks if any z-index needs to be requilt pub dirty_indices: Vec, } @@ -116,7 +113,6 @@ impl Default for QuiltCache { } } -// This system generates a quilted sprite for each z-index pub fn build_quilted_terrain_sprites( query: Query<(&FloorTile, &Transform)>, mut commands: Commands, @@ -130,19 +126,15 @@ pub fn build_quilted_terrain_sprites( if cwss.state != TerrainSpriteState::WaitingForRender { return; } - let now = Instant::now(); cwss.state = TerrainSpriteState::InProgress; - // despawn all existing quilted terrain sprites for entity in query_sprites.iter() { commands.entity(entity).despawn(); } - // Map to collect all visible tiles per z-index let mut tiles_by_z: HashMap> = HashMap::new(); - - // First pass: Collect all visible tiles by z_index and compute bounds + // Calculate bounds for all visible tiles for z_index in 0..=tilemap::Z_TOTAL as usize { for (floortile, transform) in query.iter() { let is_visible = @@ -157,75 +149,83 @@ pub fn build_quilted_terrain_sprites( } } - // For each z-index, create a quilted texture + // For each z-index, create a new quilted texture, baked and composited. for (z_index, tiles) in tiles_by_z.iter() { if tiles.is_empty() { continue; } - // Find bounds for the quilted texture - let min_x = tiles.iter().map(|(pos, _)| pos.x).reduce(f32::min).unwrap(); - let max_x = tiles.iter().map(|(pos, _)| pos.x).reduce(f32::max).unwrap(); - let min_y = tiles.iter().map(|(pos, _)| pos.y).reduce(f32::min).unwrap(); - let max_y = tiles.iter().map(|(pos, _)| pos.y).reduce(f32::max).unwrap(); + // 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) + .floor() + * TILE_SIZE; + let min_y_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.y).reduce(f32::min).unwrap() + - TILE_SIZE / 2.0) + / TILE_SIZE) + .floor() + * TILE_SIZE; + let max_x_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.x).reduce(f32::max).unwrap() + + TILE_SIZE / 2.0) + / TILE_SIZE) + .ceil() + * TILE_SIZE; + let max_y_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.y).reduce(f32::max).unwrap() + + TILE_SIZE / 2.0) + / TILE_SIZE) + .ceil() + * TILE_SIZE; - // Snap min/max values to tile grid - ensure we're centered on tile centers - // Our tiles are positioned at their centers (not corners) - let min_x_aligned = (min_x / TILE_SIZE).floor() * TILE_SIZE; - let min_y_aligned = (min_y / TILE_SIZE).floor() * TILE_SIZE; - let max_x_aligned = (max_x / TILE_SIZE).ceil() * TILE_SIZE; - let max_y_aligned = (max_y / TILE_SIZE).ceil() * TILE_SIZE; - - // Calculate texture dimensions in pixels - // We use TILE_PIXELS (16.0) as the base unit for the texture size + // Calculate terrain texture dimensions in pixels let width_tiles = ((max_x_aligned - min_x_aligned) / TILE_SIZE) as u32; let height_tiles = ((max_y_aligned - min_y_aligned) / TILE_SIZE) as u32; - let width_px = width_tiles * TILE_PIXELS as u32; - let height_px = height_tiles * TILE_PIXELS as u32; + let width_px = width_tiles * TILE_PIXELS; + let height_px = height_tiles * TILE_PIXELS; - // Store the dimensions for later use (especially for adjusting the transform) quilt_cache .dimensions .insert(*z_index, (width_px, height_px)); - // Create a new texture let mut texture_data = vec![0u8; (width_px * height_px * 4) as usize]; - // Draw each tile into the texture for (pos, floortile) in tiles { - let id = floortile.id; - let texture_id = texture_ids.refs.get(&id).unwrap(); - let texture = textures.handles.get(texture_id).unwrap(); + let texture = textures + .handles + .get(texture_ids.refs.get(&floortile.id).unwrap()) + .unwrap(); + + let rel_x = pos.x - min_x_aligned; + let rel_y = pos.y - min_y_aligned; + + let tile_x = (rel_x / TILE_SIZE).round() as u32; + let tile_y = (height_tiles as f32 - 1.0 - (rel_y / TILE_SIZE).round()) as u32; + + let target_x = tile_x * TILE_PIXELS; + let target_y = tile_y * TILE_PIXELS; + let mut data: Vec<&[u8]> = vec![]; - // Fetch the source texture data if let Some(source_texture) = images.get(texture) { - // Calculate position within the quilted texture - // We need to be precise with the mapping from world coordinates to texture pixels - - // Calculate tile position relative to the quilted texture origin - // This gives us the tile index (whole number of tiles from origin) - let tile_x = ((pos.x - min_x_aligned) / TILE_SIZE).round() as u32; - let tile_y = - (height_tiles as f32 - 1.0 - ((pos.y - min_y_aligned) / TILE_SIZE).round()) - as u32; - // Copy the tile texture into the quilted texture - // Convert from tile indices to pixel coordinates in the output texture - if let Some(data) = &source_texture.data { - blit_texture( - data, - &mut texture_data, - source_texture.size().x as u32, - source_texture.size().y as u32, - width_px, - height_px, - (tile_x * TILE_PIXELS as u32) + (TILE_PIXELS / 2.0) as u32, - (tile_y * TILE_PIXELS as u32) + (TILE_PIXELS / 2.0) as u32, - ); + 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) + data.push(_data); } + 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, + ); } } - // Create a new image asset let quilted_texture = Image::new_fill( render_resource::Extent3d { width: width_px, @@ -237,30 +237,28 @@ pub fn build_quilted_terrain_sprites( render_resource::TextureFormat::Rgba8UnormSrgb, RenderAssetUsages::RENDER_WORLD, ); - let texture_handle = images.add(quilted_texture); - // Calculate the center position of the quilted sprite - // Using the aligned coordinates to ensure proper centering let center_x = min_x_aligned + (max_x_aligned - min_x_aligned) / 2.0; let center_y = min_y_aligned + (max_y_aligned - min_y_aligned) / 2.0; - // Spawn the quilted sprite commands.spawn(( Sprite { image: texture_handle, ..Default::default() }, - Transform::from_xyz(center_x, center_y, (*z_index as f32 - Z_BELOW) * TILE_SIZE) - .with_scale(Vec3::splat(PIXEL_RATIO)), + Transform::from_xyz( + center_x + TILE_SIZE / 2.0, + center_y + TILE_SIZE / 2.0, + (*z_index as f32 - Z_BELOW) * TILE_SIZE, + ) + .with_scale(Vec3::splat(PIXEL_RATIO)), Visibility::Hidden, TerrainSprite { z_index: *z_index }, )); } - // Clear any dirty flags quilt_cache.dirty_indices.clear(); - cwss.state = TerrainSpriteState::RenderReady; println!( "Quilted world sprites built. Elapsed: {:.2?}", @@ -270,7 +268,7 @@ pub fn build_quilted_terrain_sprites( // Helper function to blit a texture onto another texture fn blit_texture( - source: &[u8], + sources: Vec<&[u8]>, target: &mut [u8], source_width: u32, source_height: u32, @@ -283,22 +281,31 @@ fn blit_texture( if y + offset_y >= target_height { continue; } - for x in 0..source_width { if x + offset_x >= target_width { 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; - // Only copy non-transparent pixels - if source.len() > source_idx + 3 && source[source_idx + 3] > 0 { - target[target_idx] = source[source_idx]; // R - target[target_idx + 1] = source[source_idx + 1]; // G - target[target_idx + 2] = source[source_idx + 2]; // B - target[target_idx + 3] = source[source_idx + 3]; // A + // New colour + let mut r: u64 = 0; + let mut g: u64 = 0; + let mut b: u64 = 0; + + 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; } + 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; } } }