average composite on terrain sprites

This commit is contained in:
2025-05-06 22:31:31 +01:00
parent 6f82b904fe
commit 6f4de57f1b
6 changed files with 93 additions and 115 deletions
+2 -30
View File
@@ -315,9 +315,6 @@ fn calculate_path(tilemap: &TileMap, start: IVec3, goal: IVec3) -> Vec<Vec3> {
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<IVec3, IVec3>, mut current: IVec3) -> Vec
}
pub fn spawn_citizens(mut commands: Commands, asset_server: Res<AssetServer>) {
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<AssetServer>) {
commands.spawn(Citizen::new(&asset_server, position));
}
}
// pub fn check_citizen_positions_for_chunks(
// query: Query<&Transform, With<Ambulatory>>,
// chunk_map: Res<tilemap::ChunkMap>,
// mut event_writer: EventWriter<tilemap::LoadChunkEvent>,
// ) {
// 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,
// // });
// // }
// }
// }
+2 -3
View File
@@ -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;
+1 -1
View File
@@ -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(
+1 -3
View File
@@ -76,7 +76,6 @@ pub struct TileMap {
pub fixture_tiles: HashMap<IVec3, (u32, bool, [u32; 8])>,
}
// This system updates sprite visibility based on current z-index
pub fn update_tile_visibility(
z_index: Res<game::ZIndex>,
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
+7 -5
View File
@@ -46,6 +46,7 @@ pub fn handle_tile_occlusion_updates(
)>,
mut cwss: ResMut<CurrentWorldSpriteState>,
) {
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<LoadChunkEvent>) {
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),
+74 -67
View File
@@ -74,7 +74,6 @@ pub fn initialize_textures(mut commands: Commands, asset_server: Res<AssetServer
asset_server.load(BEDROCK_WALL_PATH),
);
// Insert the textures resource into the world
commands.insert_resource(Textures { handles: textures });
commands.insert_resource(TextureIDs { refs: texture_ids });
}
@@ -101,9 +100,7 @@ pub struct TerrainSprite {
#[derive(Resource)]
pub struct QuiltCache {
// Tracks the dimensions of the quilted texture for each z-index
pub dimensions: HashMap<usize, (u32, u32)>,
// Tracks if any z-index needs to be requilt
pub dirty_indices: Vec<usize>,
}
@@ -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<usize, Vec<(Vec2, &FloorTile)>> = 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 {
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,
&mut texture_data,
data, // tile
&mut texture_data, // terrain
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,
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)
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;
}
}
}