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
+80 -73
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 {
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;
}
}
}